Skip to content

Commit

Permalink
fix: allow for custom variants in Text (with factory) (#3660)
Browse files Browse the repository at this point in the history
  • Loading branch information
DimitarNestorov authored Mar 6, 2023
1 parent f4c87e9 commit eb5ce8d
Show file tree
Hide file tree
Showing 25 changed files with 289 additions and 191 deletions.
12 changes: 10 additions & 2 deletions docs/docs/guides/04-fonts.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,6 @@ Platform.select({
#### Using `configureFonts` helper
<div style={{display: 'none'}}>
* If there is a need to create a custom font variant, prepare its config object including required all fonts properties. After that, defined `fontConfig` has to be passed under the <b>`variant`</b> name as `config` into the params object:
```js
Expand Down Expand Up @@ -428,7 +427,16 @@ export default function Main() {
);
}
```
</div>
If you're using TypeScript you will need to create a custom `Text` component which accepts your custom variants:
```typescript
import { customText } from 'react-native-paper'

// Use this instead of importing `Text` from `react-native-paper`
export const Text = customText<'customVariant'>()
```
* In order to override one of the available `variant`'s font properties, pass the modified `fontConfig` under specific <b>`variant`</b> name as `config` into the params object:
Expand Down
36 changes: 34 additions & 2 deletions example/src/Examples/TextExample.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import { Platform, StyleSheet, View } from 'react-native';

import {
Caption,
configureFonts,
Headline,
MD3LightTheme,
Paragraph,
Provider,
Subheading,
Text,
customText,
Title,
} from 'react-native-paper';

import { useExampleTheme } from '..';
import ScreenWrapper from '../ScreenWrapper';

const Text = customText<'customVariant'>();

const TextExample = () => {
const { isV3 } = useExampleTheme();

const fontConfig = {
customVariant: {
fontFamily: Platform.select({
ios: 'Noteworthy',
default: 'serif',
}),
fontWeight: '400',
letterSpacing: Platform.select({
ios: 7,
default: 4.6,
}),
lineHeight: 54,
fontSize: 40,
},
} as const;

const theme = {
...MD3LightTheme,
fonts: configureFonts({ config: fontConfig }),
};
return (
<ScreenWrapper>
<View style={styles.container}>
Expand Down Expand Up @@ -79,6 +105,12 @@ const TextExample = () => {
<Text style={styles.text} variant="bodySmall">
Body Small
</Text>

<Provider theme={theme}>
<Text style={styles.text} variant="customVariant">
Custom Variant
</Text>
</Provider>
</>
)}
</View>
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 color from 'color';
import { useInternalTheme } from '../../core/theming';
import { white } from '../../styles/themes/v2/colors';
import type { $RemoveChildren, MD3TypescaleKey, ThemeProp } from '../../types';
import Text from '../Typography/Text';
import Text, { TextRef } from '../Typography/Text';
import { modeTextVariant } from './utils';

type TitleString = {
Expand All @@ -39,7 +39,7 @@ export type Props = $RemoveChildren<typeof View> & {
/**
* Reference for the title.
*/
titleRef?: React.RefObject<Text>;
titleRef?: React.RefObject<TextRef>;
/**
* @deprecated Deprecated in v5.x
* Text for the subtitle.
Expand Down
55 changes: 22 additions & 33 deletions src/components/Typography/AnimatedText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import * as React from 'react';
import { Animated, I18nManager, StyleSheet, TextStyle } from 'react-native';

import { useInternalTheme } from '../../core/theming';
import { Font, ThemeProp, MD3TypescaleKey } from '../../types';
import type { ThemeProp } from '../../types';
import type { VariantProp } from './types';

type Props = React.ComponentPropsWithRef<typeof Animated.Text> & {
type Props<T> = React.ComponentPropsWithRef<typeof Animated.Text> & {
/**
* Variant defines appropriate text styles for type role and its size.
* Available variants:
Expand All @@ -19,7 +20,7 @@ type Props = React.ComponentPropsWithRef<typeof Animated.Text> & {
*
* Body: `bodyLarge`, `bodyMedium`, `bodySmall`
*/
variant?: keyof typeof MD3TypescaleKey;
variant?: VariantProp<T>;
style?: TextStyle;
/**
* @optional
Expand All @@ -37,44 +38,29 @@ function AnimatedText({
theme: themeOverrides,
variant,
...rest
}: Props) {
}: Props<never>) {
const theme = useInternalTheme(themeOverrides);
const writingDirection = I18nManager.getConstants().isRTL ? 'rtl' : 'ltr';

if (theme.isV3 && variant) {
const stylesByVariant = Object.keys(MD3TypescaleKey).reduce(
(acc, key) => {
const { fontSize, fontWeight, lineHeight, letterSpacing, fontFamily } =
theme.fonts[key as keyof typeof MD3TypescaleKey];

return {
...acc,
[key]: {
fontFamily,
fontSize,
fontWeight,
lineHeight: lineHeight,
letterSpacing,
color: theme.colors.onSurface,
},
};
},
{} as {
[key in MD3TypescaleKey]: {
fontSize: number;
fontWeight: Font['fontWeight'];
lineHeight: number;
letterSpacing: number;
};
}
);

const styleForVariant = stylesByVariant[variant];
const font = theme.fonts[variant];
if (typeof font !== 'object') {
throw new Error(
`Variant ${variant} was not provided properly. Valid variants are ${Object.keys(
theme.fonts
).join(', ')}.`
);
}

return (
<Animated.Text
{...rest}
style={[styleForVariant, styles.text, { writingDirection }, style]}
style={[
font,
styles.text,
{ writingDirection, color: theme.colors.onSurface },
style,
]}
/>
);
} else {
Expand Down Expand Up @@ -105,4 +91,7 @@ const styles = StyleSheet.create({
},
});

export const customAnimatedText = <T,>() =>
AnimatedText as (props: Props<T>) => JSX.Element;

export default AnimatedText;
71 changes: 34 additions & 37 deletions src/components/Typography/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
} from 'react-native';

import { useInternalTheme } from '../../core/theming';
import { Font, MD3TypescaleKey, ThemeProp } from '../../types';
import type { ThemeProp } from '../../types';
import { forwardRef } from '../../utils/forwardRef';
import type { VariantProp } from './types';

export type Props = React.ComponentProps<typeof NativeText> & {
export type Props<T> = React.ComponentProps<typeof NativeText> & {
/**
* @supported Available in v5.x with theme version 3
*
Expand All @@ -28,12 +29,16 @@ export type Props = React.ComponentProps<typeof NativeText> & {
*
* Body: `bodyLarge`, `bodyMedium`, `bodySmall`
*/
variant?: keyof typeof MD3TypescaleKey;
variant?: VariantProp<T>;
children: React.ReactNode;
theme?: ThemeProp;
style?: StyleProp<TextStyle>;
};

export type TextRef = React.ForwardedRef<{
setNativeProps(args: Object): void;
}>;

// @component-group Typography

/**
Expand Down Expand Up @@ -77,10 +82,9 @@ export type Props = React.ComponentProps<typeof NativeText> & {
*
* @extends Text props https://reactnative.dev/docs/text#props
*/

const Text: React.ForwardRefRenderFunction<{}, Props> = (
{ style, variant, theme: initialTheme, ...rest }: Props,
ref
const Text = (
{ style, variant, theme: initialTheme, ...rest }: Props<string>,
ref: TextRef
) => {
const root = React.useRef<NativeText | null>(null);
// FIXME: destructure it in TS 4.6+
Expand All @@ -92,39 +96,24 @@ const Text: React.ForwardRefRenderFunction<{}, Props> = (
}));

if (theme.isV3 && variant) {
const stylesByVariant = Object.keys(MD3TypescaleKey).reduce(
(acc, key) => {
const { fontSize, fontWeight, lineHeight, letterSpacing, fontFamily } =
theme.fonts[key as keyof typeof MD3TypescaleKey];

return {
...acc,
[key]: {
fontFamily,
fontSize,
fontWeight,
lineHeight,
letterSpacing,
color: theme.colors.onSurface,
},
};
},
{} as {
[key in MD3TypescaleKey]: {
fontSize: number;
fontWeight: Font['fontWeight'];
lineHeight: number;
letterSpacing: number;
};
}
);

const styleForVariant = stylesByVariant[variant];
const font = theme.fonts[variant];
if (typeof font !== 'object') {
throw new Error(
`Variant ${variant} was not provided properly. Valid variants are ${Object.keys(
theme.fonts
).join(', ')}.`
);
}

return (
<NativeText
ref={root}
style={[styleForVariant, styles.text, { writingDirection }, style]}
style={[
font,
styles.text,
{ writingDirection, color: theme.colors.onSurface },
style,
]}
{...rest}
/>
);
Expand All @@ -150,4 +139,12 @@ const styles = StyleSheet.create({
},
});

export default forwardRef(Text);
type TextComponent<T> = (
props: Props<T> & { ref?: React.RefObject<TextRef> }
) => JSX.Element;

const Component = forwardRef(Text) as TextComponent<never>;

export const customText = <T,>() => Component as unknown as TextComponent<T>;

export default Component;
5 changes: 5 additions & 0 deletions src/components/Typography/types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { MD3TypescaleKey } from '../../types';

export type VariantProp<T> =
| (T extends string ? (string extends T ? never : T) : never)
| keyof typeof MD3TypescaleKey;
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,6 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
style={
Array [
Object {
"color": "rgba(28, 27, 31, 1)",
"fontFamily": "System",
"fontSize": 22,
"fontWeight": "400",
Expand All @@ -752,6 +751,7 @@ exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and A
"textAlign": "left",
},
Object {
"color": "rgba(28, 27, 31, 1)",
"writingDirection": "ltr",
},
Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ exports[`can render leading checkbox control 1`] = `
style={
Array [
Object {
"color": "rgba(28, 27, 31, 1)",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
Expand All @@ -140,6 +139,7 @@ exports[`can render leading checkbox control 1`] = `
"textAlign": "left",
},
Object {
"color": "rgba(28, 27, 31, 1)",
"writingDirection": "ltr",
},
Array [
Expand Down Expand Up @@ -213,7 +213,6 @@ exports[`can render the Android checkbox on different platforms 1`] = `
style={
Array [
Object {
"color": "rgba(28, 27, 31, 1)",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
Expand All @@ -224,6 +223,7 @@ exports[`can render the Android checkbox on different platforms 1`] = `
"textAlign": "left",
},
Object {
"color": "rgba(28, 27, 31, 1)",
"writingDirection": "ltr",
},
Array [
Expand Down Expand Up @@ -412,7 +412,6 @@ exports[`can render the iOS checkbox on different platforms 1`] = `
style={
Array [
Object {
"color": "rgba(28, 27, 31, 1)",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
Expand All @@ -423,6 +422,7 @@ exports[`can render the iOS checkbox on different platforms 1`] = `
"textAlign": "left",
},
Object {
"color": "rgba(28, 27, 31, 1)",
"writingDirection": "ltr",
},
Array [
Expand Down Expand Up @@ -575,7 +575,6 @@ exports[`renders unchecked 1`] = `
style={
Array [
Object {
"color": "rgba(28, 27, 31, 1)",
"fontFamily": "System",
"fontSize": 16,
"fontWeight": "400",
Expand All @@ -586,6 +585,7 @@ exports[`renders unchecked 1`] = `
"textAlign": "left",
},
Object {
"color": "rgba(28, 27, 31, 1)",
"writingDirection": "ltr",
},
Array [
Expand Down
Loading

0 comments on commit eb5ce8d

Please sign in to comment.