@boostdev/design-system-react-native 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +10 -0
- package/dist/index.cjs +587 -535
- package/dist/index.d.cts +35 -4
- package/dist/index.d.ts +35 -4
- package/dist/index.js +440 -492
- package/package.json +1 -1
- package/src/components/interaction/Button/Button.spec.tsx +17 -0
- package/src/components/interaction/Button/Button.tsx +8 -2
- package/src/components/interaction/Dialog/Dialog.tsx +2 -9
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +2 -13
- package/src/components/interaction/Rating/Rating.tsx +2 -1
- package/src/components/interaction/SideSheet/SideSheet.tsx +2 -11
- package/src/components/interaction/Toast/Toast.tsx +2 -9
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +2 -1
- package/src/components/interaction/form/Combobox/Combobox.tsx +2 -10
- package/src/components/interaction/form/FileInput/FileInput.tsx +2 -1
- package/src/components/interaction/form/FormInput/FormInput.spec.tsx +11 -9
- package/src/components/interaction/form/FormInput/FormInput.tsx +4 -10
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +2 -9
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +2 -1
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +40 -0
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +8 -2
- package/src/components/interaction/form/Select/Select.tsx +2 -11
- package/src/components/interaction/form/Slider/Slider.tsx +2 -9
- package/src/components/interaction/form/Textarea/Textarea.tsx +2 -7
- package/src/components/interaction/form/atoms/Label.tsx +2 -1
- package/src/components/interaction/form/atoms/Message.tsx +2 -1
- package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -1
- package/src/components/ui/Accordion/Accordion.tsx +2 -11
- package/src/components/ui/Alert/Alert.tsx +2 -1
- package/src/components/ui/Avatar/Avatar.tsx +2 -1
- package/src/components/ui/Badge/Badge.spec.tsx +28 -0
- package/src/components/ui/Badge/Badge.tsx +9 -3
- package/src/components/ui/Breadcrumb/Breadcrumb.tsx +2 -1
- package/src/components/ui/Calendar/Calendar.tsx +2 -8
- package/src/components/ui/Carousel/Carousel.tsx +2 -12
- package/src/components/ui/Collapsible/Collapsible.tsx +2 -12
- package/src/components/ui/DescriptionList/DescriptionList.tsx +2 -1
- package/src/components/ui/Link/Link.tsx +2 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.tsx +2 -1
- package/src/components/ui/Pagination/Pagination.tsx +2 -1
- package/src/components/ui/Progress/Progress.tsx +2 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.tsx +2 -1
- package/src/components/ui/Table/Table.tsx +2 -9
- package/src/components/ui/Tabs/Tabs.tsx +2 -10
- package/src/components/ui/Tooltip/Tooltip.tsx +2 -1
- package/src/components/ui/Typography/Typography.mdx +2 -0
- package/src/components/ui/Typography/Typography.spec.tsx +59 -0
- package/src/components/ui/Typography/Typography.tsx +18 -2
- package/src/index.ts +2 -0
- package/src/native/ScaledText.guard.spec.ts +25 -0
- package/src/native/ScaledText.spec.tsx +77 -0
- package/src/native/ScaledText.tsx +30 -0
- package/src/native/fontScale.spec.ts +35 -0
- package/src/native/fontScale.ts +15 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AccessibilityRole, StyleSheet, View,
|
|
1
|
+
import { AccessibilityRole, StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { ScaledText as Text } from '../../../native/ScaledText';
|
|
2
3
|
import { spacing, font, border, lineHeight } from '../../../native/tokens';
|
|
3
4
|
import { useTheme } from '../../../native/ThemeContext';
|
|
4
5
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { StyleSheet, View,
|
|
1
|
+
import { StyleSheet, View, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { ScaledText as Text } from '../../../native/ScaledText';
|
|
2
3
|
import { spacing, font, border, lineHeight } from '../../../native/tokens';
|
|
3
4
|
import { useTheme } from '../../../native/ThemeContext';
|
|
4
5
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { StyleSheet, View,
|
|
1
|
+
import { StyleSheet, View, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { ScaledText as Text } from '../../../native/ScaledText';
|
|
2
3
|
import { font } from '../../../native/tokens';
|
|
3
4
|
import { useTheme } from '../../../native/ThemeContext';
|
|
4
5
|
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
View,
|
|
5
|
-
Text,
|
|
6
|
-
ScrollView,
|
|
7
|
-
Pressable,
|
|
8
|
-
StyleProp,
|
|
9
|
-
ViewStyle,
|
|
10
|
-
} from 'react-native';
|
|
2
|
+
import { StyleSheet, View, ScrollView, Pressable, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { ScaledText as Text } from '../../../native/ScaledText';
|
|
11
4
|
import { spacing, font, border, lineHeight } from '../../../native/tokens';
|
|
12
5
|
import { useTheme } from '../../../native/ThemeContext';
|
|
13
6
|
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { ReactNode, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
StyleSheet,
|
|
5
|
-
View,
|
|
6
|
-
Text,
|
|
7
|
-
Pressable,
|
|
8
|
-
ScrollView,
|
|
9
|
-
StyleProp,
|
|
10
|
-
ViewStyle,
|
|
11
|
-
} from 'react-native';
|
|
2
|
+
import { AccessibilityRole, StyleSheet, View, Pressable, ScrollView, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { ScaledText as Text } from '../../../native/ScaledText';
|
|
12
4
|
import { spacing, font, lineHeight } from '../../../native/tokens';
|
|
13
5
|
import { useTheme } from '../../../native/ThemeContext';
|
|
14
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactNode, useState } from 'react';
|
|
2
|
-
import { StyleSheet, View,
|
|
2
|
+
import { StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { ScaledText as Text } from '../../../native/ScaledText';
|
|
3
4
|
import { spacing, font, border } from '../../../native/tokens';
|
|
4
5
|
import { useTheme } from '../../../native/ThemeContext';
|
|
5
6
|
|
|
@@ -49,6 +49,8 @@ import { Typography } from '@boostdev/design-system-components';
|
|
|
49
49
|
| `variant` | `"h1" \| "h2" \| "h3" \| "body" \| "body_s"` | `"body"` | Typographic scale |
|
|
50
50
|
| `children` | `ReactNode` | — | Text content |
|
|
51
51
|
| `style` | `StyleProp<TextStyle>` | — | Override text styles |
|
|
52
|
+
| `maxFontSizeMultiplier` | `number` | — | Overrides the ceiling on how far the text grows with the OS text size (default: from its font size, see `maxFontScaleFor`). `1` keeps the text at its own size. |
|
|
53
|
+
| `numberOfLines` | `number` | — | Truncates the text with an ellipsis after this many lines. |
|
|
52
54
|
|
|
53
55
|
## Accessibility
|
|
54
56
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import type React from 'react';
|
|
2
3
|
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
4
|
import { Typography } from './Typography';
|
|
4
5
|
|
|
@@ -6,6 +7,37 @@ function renderWithTheme(ui: React.ReactElement) {
|
|
|
6
7
|
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
// Mocks react-native's Text to expose maxFontSizeMultiplier/numberOfLines as data attributes
|
|
11
|
+
// (react-native-web drops them from the DOM). `accessibilityRole` is mapped to `role` the same
|
|
12
|
+
// way react-native-web does ("header" -> "heading") so the heading-role tests above keep working
|
|
13
|
+
// once this mock is in effect for the whole file.
|
|
14
|
+
vi.mock('react-native', async (importOriginal) => {
|
|
15
|
+
const actual = await importOriginal<typeof import('react-native')>();
|
|
16
|
+
const accessibilityRoleToWebRole: Record<string, string> = { header: 'heading' };
|
|
17
|
+
return {
|
|
18
|
+
...actual,
|
|
19
|
+
Text: ({
|
|
20
|
+
children,
|
|
21
|
+
maxFontSizeMultiplier,
|
|
22
|
+
numberOfLines,
|
|
23
|
+
adjustsFontSizeToFit,
|
|
24
|
+
minimumFontScale,
|
|
25
|
+
accessibilityRole,
|
|
26
|
+
}: Record<string, unknown> & { children?: React.ReactNode; accessibilityRole?: string }) => (
|
|
27
|
+
<span
|
|
28
|
+
data-testid="text"
|
|
29
|
+
role={accessibilityRole ? accessibilityRoleToWebRole[accessibilityRole] : undefined}
|
|
30
|
+
data-max-font-scale={String(maxFontSizeMultiplier)}
|
|
31
|
+
data-lines={String(numberOfLines)}
|
|
32
|
+
data-fit={String(adjustsFontSizeToFit)}
|
|
33
|
+
data-min-scale={String(minimumFontScale)}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</span>
|
|
37
|
+
),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
9
41
|
describe('Typography (native)', () => {
|
|
10
42
|
it('renders children text', () => {
|
|
11
43
|
renderWithTheme(<Typography>Hello world</Typography>);
|
|
@@ -47,3 +79,30 @@ describe('Typography (native)', () => {
|
|
|
47
79
|
expect(screen.getByText('Styled')).toBeInTheDocument();
|
|
48
80
|
});
|
|
49
81
|
});
|
|
82
|
+
|
|
83
|
+
describe('Typography text size', () => {
|
|
84
|
+
it('caps h1 at a multiplier of 1.3 (32 pt)', () => {
|
|
85
|
+
renderWithTheme(<Typography variant="h1">Heading</Typography>);
|
|
86
|
+
expect(screen.getByText('Heading')).toHaveAttribute('data-max-font-scale', '1.3');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('caps body at a multiplier of 2', () => {
|
|
90
|
+
renderWithTheme(<Typography variant="body">Body</Typography>);
|
|
91
|
+
expect(screen.getByText('Body')).toHaveAttribute('data-max-font-scale', '2');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('caps a large custom font size at a multiplier of 1', () => {
|
|
95
|
+
renderWithTheme(<Typography style={{ fontSize: 88 }}>Big</Typography>);
|
|
96
|
+
expect(screen.getByText('Big')).toHaveAttribute('data-max-font-scale', '1');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('uses the override multiplier when given', () => {
|
|
100
|
+
renderWithTheme(<Typography maxFontSizeMultiplier={1}>Body</Typography>);
|
|
101
|
+
expect(screen.getByText('Body')).toHaveAttribute('data-max-font-scale', '1');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('passes numberOfLines through to truncate the text', () => {
|
|
105
|
+
renderWithTheme(<Typography numberOfLines={1}>Body</Typography>);
|
|
106
|
+
expect(screen.getByText('Body')).toHaveAttribute('data-lines', '1');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { StyleSheet,
|
|
2
|
+
import { StyleSheet, StyleProp, TextStyle } from 'react-native';
|
|
3
|
+
import { ScaledText as Text } from '../../../native/ScaledText';
|
|
3
4
|
import { font, lineHeight } from '../../../native/tokens';
|
|
4
5
|
import { useTheme } from '../../../native/ThemeContext';
|
|
5
6
|
|
|
@@ -9,6 +10,13 @@ interface TypographyProps {
|
|
|
9
10
|
variant?: TypographyVariant;
|
|
10
11
|
children: ReactNode;
|
|
11
12
|
style?: StyleProp<TextStyle>;
|
|
13
|
+
/**
|
|
14
|
+
* Overrides the ceiling on how far the text grows with the OS text size (default: from its
|
|
15
|
+
* font size, see `maxFontScaleFor`). `1` keeps the text at its own size.
|
|
16
|
+
*/
|
|
17
|
+
maxFontSizeMultiplier?: number;
|
|
18
|
+
/** Truncates the text with an ellipsis after this many lines. */
|
|
19
|
+
numberOfLines?: number;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
const styles = StyleSheet.create({
|
|
@@ -52,12 +60,20 @@ const accessibilityRoles: Record<TypographyVariant, 'header' | 'text'> = {
|
|
|
52
60
|
body_s: 'text',
|
|
53
61
|
};
|
|
54
62
|
|
|
55
|
-
export function Typography({
|
|
63
|
+
export function Typography({
|
|
64
|
+
variant = 'body',
|
|
65
|
+
children,
|
|
66
|
+
style,
|
|
67
|
+
maxFontSizeMultiplier,
|
|
68
|
+
numberOfLines,
|
|
69
|
+
}: TypographyProps) {
|
|
56
70
|
const { colors } = useTheme();
|
|
57
71
|
return (
|
|
58
72
|
<Text
|
|
59
73
|
accessibilityRole={accessibilityRoles[variant]}
|
|
60
74
|
style={[styles.base, styles[variant], { color: colors.colorOnBg }, style]}
|
|
75
|
+
maxFontSizeMultiplier={maxFontSizeMultiplier}
|
|
76
|
+
numberOfLines={numberOfLines}
|
|
61
77
|
>
|
|
62
78
|
{children}
|
|
63
79
|
</Text>
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type {
|
|
|
14
14
|
ReactNavigationFontStyle,
|
|
15
15
|
CreateReactNavigationThemeOptions,
|
|
16
16
|
} from './native/ReactNavigationTheme';
|
|
17
|
+
export { ScaledText, ScaledTextInput, maxFontScaleForStyle } from './native/ScaledText';
|
|
18
|
+
export { maxFontScaleFor } from './native/fontScale';
|
|
17
19
|
|
|
18
20
|
// UI
|
|
19
21
|
export { Typography } from './components/ui/Typography/Typography';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// Not `new URL('../components', import.meta.url)`: Vite statically matches that exact pattern as
|
|
6
|
+
// an asset-URL import and resolves it against the dev server's location, which under jsdom is a
|
|
7
|
+
// fake `http://localhost:3000/`, not the real file. Building the path from `dirname` avoids it.
|
|
8
|
+
const componentsDir = join(dirname(fileURLToPath(import.meta.url)), '../components');
|
|
9
|
+
|
|
10
|
+
function sourceFiles(dir: string): string[] {
|
|
11
|
+
return readdirSync(dir).flatMap((name) => {
|
|
12
|
+
const path = join(dir, name);
|
|
13
|
+
if (statSync(path).isDirectory()) return sourceFiles(path);
|
|
14
|
+
return /\.tsx?$/.test(name) && !/\.(spec|stories)\.tsx?$/.test(name) ? [path] : [];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('text in components', () => {
|
|
19
|
+
it('never takes Text or TextInput from react-native directly', () => {
|
|
20
|
+
const offenders = sourceFiles(componentsDir).filter((file) =>
|
|
21
|
+
/import\s*{[^}]*\b(Text|TextInput)\b[^}]*}\s*from\s*'react-native'/.test(readFileSync(file, 'utf8')),
|
|
22
|
+
);
|
|
23
|
+
expect(offenders).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import { maxFontScaleForStyle, ScaledText, ScaledTextInput } from './ScaledText';
|
|
4
|
+
|
|
5
|
+
describe('maxFontScaleForStyle', () => {
|
|
6
|
+
it('returns 2 for a fontSize of 16', () => {
|
|
7
|
+
expect(maxFontScaleForStyle({ fontSize: 16 })).toBe(2);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('returns 1.3 for a style array, the later style wins', () => {
|
|
11
|
+
expect(maxFontScaleForStyle([{ fontSize: 16 }, { fontSize: 28 }])).toBe(1.3);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns 2 for an undefined style', () => {
|
|
15
|
+
expect(maxFontScaleForStyle(undefined)).toBe(2);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns the override when given', () => {
|
|
19
|
+
expect(maxFontScaleForStyle({ fontSize: 16 }, 1)).toBe(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns the computed value when the override is null', () => {
|
|
23
|
+
expect(maxFontScaleForStyle({ fontSize: 16 }, null)).toBe(2);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
vi.mock('react-native', async (importOriginal) => {
|
|
28
|
+
const actual = await importOriginal<typeof import('react-native')>();
|
|
29
|
+
return {
|
|
30
|
+
...actual,
|
|
31
|
+
Text: ({
|
|
32
|
+
children,
|
|
33
|
+
maxFontSizeMultiplier,
|
|
34
|
+
numberOfLines,
|
|
35
|
+
adjustsFontSizeToFit,
|
|
36
|
+
minimumFontScale,
|
|
37
|
+
}: Record<string, unknown> & { children?: React.ReactNode }) => (
|
|
38
|
+
<span
|
|
39
|
+
data-testid="text"
|
|
40
|
+
data-max-font-scale={String(maxFontSizeMultiplier)}
|
|
41
|
+
data-lines={String(numberOfLines)}
|
|
42
|
+
data-fit={String(adjustsFontSizeToFit)}
|
|
43
|
+
data-min-scale={String(minimumFontScale)}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</span>
|
|
47
|
+
),
|
|
48
|
+
TextInput: ({
|
|
49
|
+
maxFontSizeMultiplier,
|
|
50
|
+
}: Record<string, unknown> & { children?: React.ReactNode }) => (
|
|
51
|
+
<input data-testid="text-input" data-max-font-scale={String(maxFontSizeMultiplier)} />
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('ScaledText', () => {
|
|
57
|
+
it('caps a large figure at a multiplier of 1', () => {
|
|
58
|
+
render(<ScaledText style={{ fontSize: 88 }}>7:00</ScaledText>);
|
|
59
|
+
expect(screen.getByTestId('text')).toHaveAttribute('data-max-font-scale', '1');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('uses the override multiplier when given', () => {
|
|
63
|
+
render(
|
|
64
|
+
<ScaledText style={{ fontSize: 16 }} maxFontSizeMultiplier={1}>
|
|
65
|
+
Body
|
|
66
|
+
</ScaledText>,
|
|
67
|
+
);
|
|
68
|
+
expect(screen.getByTestId('text')).toHaveAttribute('data-max-font-scale', '1');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('ScaledTextInput', () => {
|
|
73
|
+
it('caps a body input at a multiplier of 2', () => {
|
|
74
|
+
render(<ScaledTextInput style={{ fontSize: 16 }} />);
|
|
75
|
+
expect(screen.getByTestId('text-input')).toHaveAttribute('data-max-font-scale', '2');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { StyleSheet, Text, TextInput } from 'react-native';
|
|
3
|
+
import type { StyleProp, TextInputProps, TextProps, TextStyle } from 'react-native';
|
|
4
|
+
import { DEFAULT_FONT_SIZE, maxFontScaleFor } from './fontScale';
|
|
5
|
+
|
|
6
|
+
/** The ceiling for a text with this style (see `maxFontScaleFor`), unless the caller passes its own. */
|
|
7
|
+
export function maxFontScaleForStyle(style: StyleProp<TextStyle>, override?: number | null): number {
|
|
8
|
+
if (override !== undefined && override !== null) return override;
|
|
9
|
+
const fontSize = StyleSheet.flatten(style)?.fontSize;
|
|
10
|
+
return maxFontScaleFor(typeof fontSize === 'number' ? fontSize : DEFAULT_FONT_SIZE);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* React Native's `Text` with a ceiling on how far it grows with the OS text size, taken from its
|
|
15
|
+
* own font size. Pass `maxFontSizeMultiplier` to override it, e.g. `1` for text in a fixed shape.
|
|
16
|
+
*/
|
|
17
|
+
export const ScaledText = forwardRef<Text, TextProps>(function ScaledText(
|
|
18
|
+
{ maxFontSizeMultiplier, style, ...rest },
|
|
19
|
+
ref,
|
|
20
|
+
) {
|
|
21
|
+
return <Text ref={ref} style={style} maxFontSizeMultiplier={maxFontScaleForStyle(style, maxFontSizeMultiplier)} {...rest} />;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** React Native's `TextInput` with the same ceiling as `ScaledText`. */
|
|
25
|
+
export const ScaledTextInput = forwardRef<TextInput, TextInputProps>(function ScaledTextInput(
|
|
26
|
+
{ maxFontSizeMultiplier, style, ...rest },
|
|
27
|
+
ref,
|
|
28
|
+
) {
|
|
29
|
+
return <TextInput ref={ref} style={style} maxFontSizeMultiplier={maxFontScaleForStyle(style, maxFontSizeMultiplier)} {...rest} />;
|
|
30
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { maxFontScaleFor } from './fontScale';
|
|
2
|
+
|
|
3
|
+
describe('maxFontScaleFor', () => {
|
|
4
|
+
it('returns 2 for a font size of 13', () => {
|
|
5
|
+
expect(maxFontScaleFor(13)).toBe(2);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('returns 2 for a font size of 19.5', () => {
|
|
9
|
+
expect(maxFontScaleFor(19.5)).toBe(2);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('returns 1.5 for a font size of 20', () => {
|
|
13
|
+
expect(maxFontScaleFor(20)).toBe(1.5);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('returns 1.5 for a font size of 25', () => {
|
|
17
|
+
expect(maxFontScaleFor(25)).toBe(1.5);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns 1.3 for a font size of 26', () => {
|
|
21
|
+
expect(maxFontScaleFor(26)).toBe(1.3);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns 1.3 for a font size of 59', () => {
|
|
25
|
+
expect(maxFontScaleFor(59)).toBe(1.3);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns 1 for a font size of 60', () => {
|
|
29
|
+
expect(maxFontScaleFor(60)).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns 1 for a font size of 112', () => {
|
|
33
|
+
expect(maxFontScaleFor(112)).toBe(1);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How far text of this size may grow with the OS text size (iOS Dynamic Type, Android font
|
|
3
|
+
* scale), as React Native's `maxFontSizeMultiplier`. Big text grows less: a 16 pt body line may
|
|
4
|
+
* double, a 32 pt heading grows to about 42, and a figure of 60 pt or more (a countdown, a score)
|
|
5
|
+
* stays as it is.
|
|
6
|
+
*/
|
|
7
|
+
export function maxFontScaleFor(fontSize: number): number {
|
|
8
|
+
if (fontSize >= 60) return 1;
|
|
9
|
+
if (fontSize >= 26) return 1.3;
|
|
10
|
+
if (fontSize >= 20) return 1.5;
|
|
11
|
+
return 2;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** React Native's own font size for a text whose style sets none. */
|
|
15
|
+
export const DEFAULT_FONT_SIZE = 14;
|