Skip to content

Commit

Permalink
feat: add ability to extend theme TS type with custom properties (#2002)
Browse files Browse the repository at this point in the history
  • Loading branch information
Trancever authored Jun 26, 2020
1 parent 4bc030b commit 0f8644a
Show file tree
Hide file tree
Showing 68 changed files with 268 additions and 139 deletions.
70 changes: 70 additions & 0 deletions docs/pages/2.theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,76 @@ If you don't use custom theme, Paper will automatically turn animations on/off,

Otherwise, your custom theme will need to handle it manually, using React Native's [AccessibilityInfo API](https://reactnative.dev/docs/0.54/accessibilityinfo);

## Extending the theme

Keeping your own properties in the the theme is fully supported by our library:

```tsx
import * as React from 'react';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';

const theme = {
...DefaultTheme,
// Specify custom property
myOwnProperty: true,
// Specify custom property in nested object
colors: {
myOwnColor: '#BADA55',
}
};

export default function Main() {
return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}
```

### Typescript

By default it won't work well with typescript, but we can take advantage of `global augmentations` and specify the new properties that we added to the theme:

```tsx
import * as React from 'react';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';

declare global {
namespace ReactNativePaper {
interface ThemeColors {
myOwnColor: string;
}

interface Theme {
myOwnProperty: boolean;
}
}
}

const theme = {
...DefaultTheme,
// Specify custom property
myOwnProperty: true,
// Specify custom property in nested object
colors: {
myOwnColor: '#BADA55',
}
};

export default function Main() {
return (
<PaperProvider theme={theme}>
<App />
</PaperProvider>
);
}
```

As you can see, custom properties e.q. `myOwnColor` defined in nested object e.g. `colors` needs to be declared in its own interface. You'll find more information in our [example app](https://github.com/callstack/react-native-paper/blob/master/example/src/index.tsx) where we have it implemented.

## Applying a theme to a paper component

If you want to change the theme for a certain component from the library, you can directly pass the `theme` prop to the component. The theme passed as the prop is merged with the theme from the `Provider`.
Expand Down
65 changes: 61 additions & 4 deletions example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,68 @@ import {
Provider as PaperProvider,
DarkTheme,
DefaultTheme,
Theme,
} from 'react-native-paper';
import App from './RootNavigator';
import DrawerItems from './DrawerItems';
import { SafeAreaProvider } from 'react-native-safe-area-context';

// Add new typescript properties to the theme
declare global {
namespace ReactNativePaper {
interface ThemeFonts {
superLight: ThemeFont;
}
interface ThemeColors {
customColor: string;
}
interface ThemeAnimation {
customProperty: number;
}
interface Theme {
userDefinedThemeProperty: string;
}
}
}

YellowBox.ignoreWarnings(['Require cycle:']);

const PERSISTENCE_KEY = 'NAVIGATION_STATE';
const PREFERENCES_KEY = 'APP_PREFERENCES';

const CustomDarkTheme: ReactNativePaper.Theme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
customColor: '#BADA55',
},
fonts: {
...DarkTheme.fonts,
superLight: { ...DarkTheme.fonts['light'] },
},
userDefinedThemeProperty: '',
animation: {
...DarkTheme.animation,
customProperty: 1,
},
};

const CustomDefaultTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
customColor: '#BADA55',
},
fonts: {
...DefaultTheme.fonts,
superLight: { ...DefaultTheme.fonts['light'] },
},
userDefinedThemeProperty: '',
animation: {
...DefaultTheme.animation,
customProperty: 1,
},
};

const PreferencesContext = React.createContext<any>(null);

const DrawerContent = () => {
Expand Down Expand Up @@ -46,7 +97,9 @@ export default function PaperExample() {
InitialState | undefined
>();

const [theme, setTheme] = React.useState<Theme>(DefaultTheme);
const [theme, setTheme] = React.useState<ReactNativePaper.Theme>(
CustomDefaultTheme
);
const [rtl, setRtl] = React.useState<boolean>(I18nManager.isRTL);

React.useEffect(() => {
Expand Down Expand Up @@ -76,7 +129,9 @@ export default function PaperExample() {

if (preferences) {
// eslint-disable-next-line react/no-did-mount-set-state
setTheme(preferences.theme === 'dark' ? DarkTheme : DefaultTheme);
setTheme(
preferences.theme === 'dark' ? CustomDarkTheme : CustomDefaultTheme
);

if (typeof preferences.rtl === 'boolean') {
setRtl(preferences.rtl);
Expand Down Expand Up @@ -116,7 +171,9 @@ export default function PaperExample() {
const preferences = React.useMemo(
() => ({
toggleTheme: () =>
setTheme(theme => (theme === DefaultTheme ? DarkTheme : DefaultTheme)),
setTheme(theme =>
theme === CustomDefaultTheme ? CustomDarkTheme : CustomDefaultTheme
),
toggleRtl: () => setRtl(rtl => !rtl),
rtl,
theme,
Expand Down
3 changes: 1 addition & 2 deletions src/components/ActivityIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
ViewStyle,
} from 'react-native';
import { withTheme } from '../core/theming';
import { Theme } from '../types';

type Props = React.ComponentPropsWithRef<typeof View> & {
/**
Expand All @@ -32,7 +31,7 @@ type Props = React.ComponentPropsWithRef<typeof View> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

type State = {
Expand Down
3 changes: 1 addition & 2 deletions src/components/Appbar/Appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import AppbarHeader, { AppbarHeader as _AppbarHeader } from './AppbarHeader';
import Surface from '../Surface';
import { withTheme } from '../../core/theming';
import { black, white } from '../../styles/colors';
import { Theme } from '../../types';
import overlay from '../../styles/overlay';

type Props = Partial<React.ComponentPropsWithRef<typeof View>> & {
Expand All @@ -28,7 +27,7 @@ type Props = Partial<React.ComponentPropsWithRef<typeof View>> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
style?: StyleProp<ViewStyle>;
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/Appbar/AppbarContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Text from '../Typography/Text';
import { withTheme } from '../../core/theming';
import { white } from '../../styles/colors';

import { Theme, $RemoveChildren } from '../../types';
import { $RemoveChildren } from '../../types';

type Props = $RemoveChildren<typeof View> & {
/**
Expand Down Expand Up @@ -50,7 +50,7 @@ type Props = $RemoveChildren<typeof View> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

/**
Expand Down
3 changes: 1 addition & 2 deletions src/components/Appbar/AppbarHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import overlay from '../../styles/overlay';
import Appbar, { DEFAULT_APPBAR_HEIGHT } from './Appbar';
import shadow from '../../styles/shadow';
import { withTheme } from '../../core/theming';
import { Theme } from '../../types';
import { APPROX_STATUSBAR_HEIGHT } from '../../constants';

type Props = React.ComponentProps<typeof Appbar> & {
Expand All @@ -32,7 +31,7 @@ type Props = React.ComponentProps<typeof Appbar> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
style?: StyleProp<ViewStyle>;
};

Expand Down
3 changes: 1 addition & 2 deletions src/components/Avatar/AvatarIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import color from 'color';
import Icon from '../Icon';
import { withTheme } from '../../core/theming';
import { white } from '../../styles/colors';
import { Theme } from '../../types';
import { IconSource } from './../Icon';

const defaultSize = 64;
Expand All @@ -26,7 +25,7 @@ type Props = React.ComponentPropsWithRef<typeof View> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

/**
Expand Down
3 changes: 1 addition & 2 deletions src/components/Avatar/AvatarImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
ImageSourcePropType,
} from 'react-native';
import { withTheme } from '../../core/theming';
import { Theme } from '../../types';

const defaultSize = 64;

Expand All @@ -25,7 +24,7 @@ type Props = React.ComponentPropsWithRef<typeof View> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

/**
Expand Down
3 changes: 1 addition & 2 deletions src/components/Avatar/AvatarText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import Color from 'color';
import Text from '../Typography/Text';
import { withTheme } from '../../core/theming';
import { white } from '../../styles/colors';
import { Theme } from '../../types';

const defaultSize = 64;

Expand Down Expand Up @@ -38,7 +37,7 @@ type Props = React.ComponentPropsWithRef<typeof View> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

/**
Expand Down
3 changes: 1 addition & 2 deletions src/components/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Animated, StyleSheet, StyleProp, TextStyle } from 'react-native';
import color from 'color';
import { black, white } from '../styles/colors';
import { withTheme } from '../core/theming';
import { Theme } from '../types';

const defaultSize = 20;

Expand All @@ -25,7 +24,7 @@ type Props = React.ComponentProps<typeof Animated.Text> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

type State = {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Text from './Typography/Text';
import Button from './Button';
import Icon, { IconSource } from './Icon';
import { withTheme } from '../core/theming';
import { Theme, $RemoveChildren } from '../types';
import { $RemoveChildren } from '../types';
import shadow from '../styles/shadow';

const ELEVATION = 1;
Expand Down Expand Up @@ -47,7 +47,7 @@ type Props = $RemoveChildren<typeof Surface> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

type State = {
Expand Down
3 changes: 1 addition & 2 deletions src/components/BottomNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import TouchableRipple from './TouchableRipple';
import Text from './Typography/Text';
import { black, white } from '../styles/colors';
import { withTheme } from '../core/theming';
import { Theme } from '../types';

type Route = {
key: string;
Expand Down Expand Up @@ -219,7 +218,7 @@ type Props = {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
};

type State = {
Expand Down
4 changes: 1 addition & 3 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import TouchableRipple from './TouchableRipple';
import { black, white } from '../styles/colors';
import { withTheme } from '../core/theming';

import { Theme } from '../types';

type Props = React.ComponentProps<typeof Surface> & {
/**
* Mode of the button. You can change the mode to adjust the styling to give it desired emphasis.
Expand Down Expand Up @@ -80,7 +78,7 @@ type Props = React.ComponentProps<typeof Surface> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
/**
* testID to be used on tests.
*/
Expand Down
3 changes: 1 addition & 2 deletions src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import CardCover, { CardCover as _CardCover } from './CardCover';
import CardTitle, { CardTitle as _CardTitle } from './CardTitle';
import Surface from '../Surface';
import { withTheme } from '../../core/theming';
import { Theme } from '../../types';

type Props = React.ComponentProps<typeof Surface> & {
/**
Expand All @@ -38,7 +37,7 @@ type Props = React.ComponentProps<typeof Surface> & {
/**
* @optional
*/
theme: Theme;
theme: ReactNativePaper.Theme;
/**
* Pass down testID from card props to touchable
*/
Expand Down
Loading

0 comments on commit 0f8644a

Please sign in to comment.