Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow for custom variants in Text (with factory) #3660

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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