diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java
index cf0c8b141b0..61900f19f6f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/CustomStyleSpan.java
@@ -12,9 +12,10 @@ import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.facebook.infer.annotation.Nullsafe;
+@Nullsafe(Nullsafe.Mode.LOCAL)
public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
/**
@@ -40,7 +41,7 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
int fontWeight,
@Nullable String fontFeatureSettings,
@Nullable String fontFamily,
- @NonNull AssetManager assetManager) {
+ AssetManager assetManager) {
mStyle = fontStyle;
mWeight = fontWeight;
mFeatureSettings = fontFeatureSettings;
@@ -54,21 +55,18 @@ public class CustomStyleSpan extends MetricAffectingSpan implements ReactSpan {
}
@Override
- public void updateMeasureState(@NonNull TextPaint paint) {
+ public void updateMeasureState(TextPaint paint) {
apply(paint, mStyle, mWeight, mFeatureSettings, mFontFamily, mAssetManager);
}
- /** Returns {@link Typeface#NORMAL} or {@link Typeface#ITALIC}. */
public int getStyle() {
- return (mStyle == ReactTextShadowNode.UNSET ? 0 : mStyle);
+ return mStyle == ReactBaseTextShadowNode.UNSET ? Typeface.NORMAL : mStyle;
}
- /** Returns {@link Typeface#NORMAL} or {@link Typeface#BOLD}. */
public int getWeight() {
- return (mWeight == ReactTextShadowNode.UNSET ? 0 : mWeight);
+ return mWeight == ReactBaseTextShadowNode.UNSET ? TypefaceStyle.NORMAL : mWeight;
}
- /** Returns the font family set for this StyleSpan. */
public @Nullable String getFontFamily() {
return mFontFamily;
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java
index 32f0219db87..077f6006887 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java
@@ -10,31 +10,39 @@ package com.facebook.react.views.text;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Typeface;
-import android.os.Build;
import android.util.SparseArray;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
+import com.facebook.infer.annotation.Nullsafe;
import java.util.HashMap;
import java.util.Map;
/**
- * Class responsible to load and cache Typeface objects. It will first try to load typefaces inside
- * the assets/fonts folder and if it doesn't find the right Typeface in that folder will fall back
- * on the best matching system Typeface The supported custom fonts extensions are .ttf and .otf. For
- * each font family the bold, italic and bold_italic variants are supported. Given a "family" font
- * family the files in the assets/fonts folder need to be family.ttf(.otf) family_bold.ttf(.otf)
- * family_italic.ttf(.otf) and family_bold_italic.ttf(.otf)
+ * Responsible for loading and caching Typeface objects.
+ *
+ *
This will first try to load a typeface from the assets/fonts folder. If one is not found in
+ * that folder, this will fallback to the best matching system typeface.
+ *
+ *
Custom fonts support the extensions `.ttf` and `.otf` and the variants `bold`, `italic`, and
+ * `bold_italic`. For example, given a font named "ExampleFontFamily", the following are supported:
+ *
+ *
+ * - ExampleFontFamily.ttf (or .otf)
+ *
- ExampleFontFamily_bold.ttf (or .otf)
+ *
- ExampleFontFamily_italic.ttf (or .otf)
+ *
- ExampleFontFamily_bold_italic.ttf (or .otf)
*/
+@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactFontManager {
+ // NOTE: Indices in `EXTENSIONS` correspond to the `TypeFace` style constants.
private static final String[] EXTENSIONS = {"", "_bold", "_italic", "_bold_italic"};
private static final String[] FILE_EXTENSIONS = {".ttf", ".otf"};
private static final String FONTS_ASSET_PATH = "fonts/";
private static ReactFontManager sReactFontManagerInstance;
- private final Map mFontCache;
+ private final Map mFontCache;
private final Map mCustomTypefaceCache;
private ReactFontManager() {
@@ -49,36 +57,43 @@ public class ReactFontManager {
return sReactFontManagerInstance;
}
- public @Nullable Typeface getTypeface(
- String fontFamilyName, int style, AssetManager assetManager) {
- return getTypeface(fontFamilyName, style, 0, assetManager);
+ public Typeface getTypeface(String fontFamilyName, int style, AssetManager assetManager) {
+ return getTypeface(fontFamilyName, new TypefaceStyle(style), assetManager);
}
- public @Nullable Typeface getTypeface(
+ public Typeface getTypeface(
+ String fontFamilyName, int weight, boolean italic, AssetManager assetManager) {
+ return getTypeface(fontFamilyName, new TypefaceStyle(weight, italic), assetManager);
+ }
+
+ public Typeface getTypeface(
String fontFamilyName, int style, int weight, AssetManager assetManager) {
+ return getTypeface(fontFamilyName, new TypefaceStyle(style, weight), assetManager);
+ }
+
+ public Typeface getTypeface(
+ String fontFamilyName, TypefaceStyle typefaceStyle, AssetManager assetManager) {
if (mCustomTypefaceCache.containsKey(fontFamilyName)) {
- Typeface typeface = mCustomTypefaceCache.get(fontFamilyName);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && weight >= 100 && weight <= 1000) {
- return Typeface.create(typeface, weight, (style & Typeface.ITALIC) != 0);
- }
- return Typeface.create(typeface, style);
+ // Apply `typefaceStyle` because custom fonts configure variants using `app:fontStyle` and
+ // `app:fontWeight` in their resource XML configuration file.
+ return typefaceStyle.apply(mCustomTypefaceCache.get(fontFamilyName));
}
- FontFamily fontFamily = mFontCache.get(fontFamilyName);
- if (fontFamily == null) {
- fontFamily = new FontFamily();
- mFontCache.put(fontFamilyName, fontFamily);
+ AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName);
+ if (assetFontFamily == null) {
+ assetFontFamily = new AssetFontFamily();
+ mFontCache.put(fontFamilyName, assetFontFamily);
}
- Typeface typeface = fontFamily.getTypeface(style);
- if (typeface == null) {
- typeface = createTypeface(fontFamilyName, style, assetManager);
- if (typeface != null) {
- fontFamily.setTypeface(style, typeface);
- }
- }
+ int style = typefaceStyle.getNearestStyle();
- return typeface;
+ Typeface assetTypeface = assetFontFamily.getTypefaceForStyle(style);
+ if (assetTypeface == null) {
+ assetTypeface = createAssetTypeface(fontFamilyName, style, assetManager);
+ assetFontFamily.setTypefaceForStyle(style, assetTypeface);
+ }
+ // Do not apply `typefaceStyle` because asset font files already incorporate the style.
+ return assetTypeface;
}
/*
@@ -88,7 +103,7 @@ public class ReactFontManager {
*
* ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi);
*/
- public void addCustomFont(@NonNull Context context, @NonNull String fontFamily, int fontId) {
+ public void addCustomFont(Context context, String fontFamily, int fontId) {
Typeface font = ResourcesCompat.getFont(context, fontId);
if (font != null) {
mCustomTypefaceCache.put(fontFamily, font);
@@ -106,16 +121,16 @@ public class ReactFontManager {
*/
public void setTypeface(String fontFamilyName, int style, Typeface typeface) {
if (typeface != null) {
- FontFamily fontFamily = mFontCache.get(fontFamilyName);
- if (fontFamily == null) {
- fontFamily = new FontFamily();
- mFontCache.put(fontFamilyName, fontFamily);
+ AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName);
+ if (assetFontFamily == null) {
+ assetFontFamily = new AssetFontFamily();
+ mFontCache.put(fontFamilyName, assetFontFamily);
}
- fontFamily.setTypeface(style, typeface);
+ assetFontFamily.setTypefaceForStyle(style, typeface);
}
}
- private static @Nullable Typeface createTypeface(
+ private static Typeface createAssetTypeface(
String fontFamilyName, int style, AssetManager assetManager) {
String extension = EXTENSIONS[style];
for (String fileExtension : FILE_EXTENSIONS) {
@@ -129,27 +144,27 @@ public class ReactFontManager {
try {
return Typeface.createFromAsset(assetManager, fileName);
} catch (RuntimeException e) {
- // unfortunately Typeface.createFromAsset throws an exception instead of returning null
- // if the typeface doesn't exist
+ // If the typeface asset does not exist, try another extension.
+ continue;
}
}
-
return Typeface.create(fontFamilyName, style);
}
- private static class FontFamily {
+ /** Responsible for caching typefaces for each custom font family. */
+ private static class AssetFontFamily {
private SparseArray mTypefaceSparseArray;
- private FontFamily() {
+ private AssetFontFamily() {
mTypefaceSparseArray = new SparseArray<>(4);
}
- public Typeface getTypeface(int style) {
+ public @Nullable Typeface getTypefaceForStyle(int style) {
return mTypefaceSparseArray.get(style);
}
- public void setTypeface(int style, Typeface typeface) {
+ public void setTypefaceForStyle(int style, Typeface typeface) {
mTypefaceSparseArray.put(style, typeface);
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java
index ccbf3af90be..85948ef78e8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.java
@@ -9,38 +9,54 @@ package com.facebook.react.views.text;
import android.content.res.AssetManager;
import android.graphics.Typeface;
-import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.Nullable;
-import com.facebook.common.logging.FLog;
+import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.ReadableArray;
import java.util.ArrayList;
import java.util.List;
+@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactTypefaceUtils {
- private static final String TAG = "ReactTypefaceUtils";
- public static final int UNSET = -1;
public static int parseFontWeight(@Nullable String fontWeightString) {
- int fontWeightNumeric =
- fontWeightString != null ? parseNumericFontWeight(fontWeightString) : UNSET;
- int fontWeight = fontWeightNumeric != UNSET ? fontWeightNumeric : Typeface.NORMAL;
-
- if ("bold".equals(fontWeightString)) fontWeight = Typeface.BOLD;
- else if ("normal".equals(fontWeightString)) fontWeight = Typeface.NORMAL;
-
- return fontWeight;
+ if (fontWeightString != null) {
+ switch (fontWeightString) {
+ case "100":
+ return 100;
+ case "200":
+ return 200;
+ case "300":
+ return 300;
+ case "normal":
+ case "400":
+ return 400;
+ case "500":
+ return 500;
+ case "600":
+ return 600;
+ case "bold":
+ case "700":
+ return 700;
+ case "800":
+ return 800;
+ case "900":
+ return 900;
+ }
+ }
+ return ReactBaseTextShadowNode.UNSET;
}
public static int parseFontStyle(@Nullable String fontStyleString) {
- int fontStyle = UNSET;
- if ("italic".equals(fontStyleString)) {
- fontStyle = Typeface.ITALIC;
- } else if ("normal".equals(fontStyleString)) {
- fontStyle = Typeface.NORMAL;
+ if (fontStyleString != null) {
+ if ("italic".equals(fontStyleString)) {
+ return Typeface.ITALIC;
+ }
+ if ("normal".equals(fontStyleString)) {
+ return Typeface.NORMAL;
+ }
}
-
- return fontStyle;
+ return ReactBaseTextShadowNode.UNSET;
}
public static @Nullable String parseFontVariant(@Nullable ReadableArray fontVariantArray) {
@@ -80,67 +96,14 @@ public class ReactTypefaceUtils {
@Nullable Typeface typeface,
int style,
int weight,
- @Nullable String family,
+ @Nullable String fontFamilyName,
AssetManager assetManager) {
- int oldStyle;
- if (typeface == null) {
- oldStyle = Typeface.NORMAL;
+ TypefaceStyle typefaceStyle = new TypefaceStyle(style, weight);
+ if (fontFamilyName == null) {
+ return typefaceStyle.apply(typeface == null ? Typeface.DEFAULT : typeface);
} else {
- oldStyle = typeface.getStyle();
+ return ReactFontManager.getInstance()
+ .getTypeface(fontFamilyName, typefaceStyle, assetManager);
}
-
- int newStyle = oldStyle;
- boolean italic = false;
- if (weight == UNSET) weight = Typeface.NORMAL;
- if (style == Typeface.ITALIC) italic = true;
- boolean UNDER_SDK_28 = Build.VERSION.SDK_INT < Build.VERSION_CODES.P;
- boolean applyNumericValues = !(weight < (Typeface.BOLD_ITALIC + 1) || family != null);
- boolean numericBold = UNDER_SDK_28 && weight > 699 && applyNumericValues;
- boolean numericNormal = UNDER_SDK_28 && weight < 700 && applyNumericValues;
- if (weight == Typeface.BOLD) {
- newStyle = (newStyle == Typeface.ITALIC) ? Typeface.BOLD_ITALIC : Typeface.BOLD;
- typeface = Typeface.create(typeface, newStyle);
- }
- if (weight == Typeface.NORMAL) {
- typeface = Typeface.create(typeface, Typeface.NORMAL);
- newStyle = Typeface.NORMAL;
- }
- if (style == Typeface.ITALIC) {
- newStyle = (newStyle == Typeface.BOLD) ? Typeface.BOLD_ITALIC : Typeface.ITALIC;
- typeface = Typeface.create(typeface, newStyle);
- }
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1 && weight > Typeface.BOLD_ITALIC) {
- typeface = Typeface.create(typeface, weight, italic);
- }
- if (family != null && UNDER_SDK_28 && weight > Typeface.BOLD_ITALIC) {
- FLog.d(
- TAG,
- "Support for numeric font weight numeric values with custom fonts under Android API 28 Pie is not yet supported in ReactNative.");
- }
- if (family != null) {
- typeface = ReactFontManager.getInstance().getTypeface(family, newStyle, weight, assetManager);
- }
- if (numericBold || numericNormal) {
- newStyle = numericBold ? Typeface.BOLD : Typeface.NORMAL;
- typeface = Typeface.create(typeface, newStyle);
- FLog.d(
- TAG,
- "Support for numeric font weight numeric values available only from Android API 28 Pie. Android device lower then API 28 will use normal or bold.");
- }
- return typeface;
- }
-
- /**
- * Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
- * return the weight.
- */
- private static int parseNumericFontWeight(String fontWeightString) {
- // This should be much faster than using regex to verify input and Integer.parseInt
- return fontWeightString.length() == 3
- && fontWeightString.endsWith("00")
- && fontWeightString.charAt(0) <= '9'
- && fontWeightString.charAt(0) >= '1'
- ? 100 * (fontWeightString.charAt(0) - '0')
- : UNSET;
}
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
index fe926cb743c..13311fef41b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java
@@ -7,7 +7,6 @@
package com.facebook.react.views.text;
-import android.graphics.Typeface;
import android.os.Build;
import android.text.Layout;
import android.text.TextUtils;
@@ -104,12 +103,7 @@ public class TextAttributeProps {
protected @Nullable ReactAccessibilityDelegate.AccessibilityRole mAccessibilityRole = null;
protected boolean mIsAccessibilityRoleSet = false;
- /**
- * mFontStyle can be {@link Typeface#NORMAL} or {@link Typeface#ITALIC}. mFontWeight can be {@link
- * Typeface#NORMAL} or {@link Typeface#BOLD}.
- */
protected int mFontStyle = UNSET;
-
protected int mFontWeight = UNSET;
/**
* NB: If a font family is used that does not have a style in a certain Android version (ie.
@@ -460,39 +454,12 @@ public class TextAttributeProps {
mFontFeatureSettings = TextUtils.join(", ", features);
}
- /**
- * /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they
- * can both use
- */
private void setFontWeight(@Nullable String fontWeightString) {
- int fontWeightNumeric =
- fontWeightString != null ? parseNumericFontWeight(fontWeightString) : -1;
- int fontWeight = UNSET;
- if (fontWeightNumeric >= 500 || "bold".equals(fontWeightString)) {
- fontWeight = Typeface.BOLD;
- } else if ("normal".equals(fontWeightString)
- || (fontWeightNumeric != -1 && fontWeightNumeric < 500)) {
- fontWeight = Typeface.NORMAL;
- }
- if (fontWeight != mFontWeight) {
- mFontWeight = fontWeight;
- }
+ mFontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString);
}
- /**
- * /* This code is duplicated in ReactTextInputManager /* TODO: Factor into a common place they
- * can both use
- */
private void setFontStyle(@Nullable String fontStyleString) {
- int fontStyle = UNSET;
- if ("italic".equals(fontStyleString)) {
- fontStyle = Typeface.ITALIC;
- } else if ("normal".equals(fontStyleString)) {
- fontStyle = Typeface.NORMAL;
- }
- if (fontStyle != mFontStyle) {
- mFontStyle = fontStyle;
- }
+ mFontStyle = ReactTypefaceUtils.parseFontStyle(fontStyleString);
}
private void setIncludeFontPadding(boolean includepad) {
@@ -601,21 +568,4 @@ public class TextAttributeProps {
}
return androidTextBreakStrategy;
}
-
- /**
- * Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise
- * return the weight.
- *
- *
This code is duplicated in ReactTextInputManager TODO: Factor into a common place they can
- * both use
- */
- private static int parseNumericFontWeight(String fontWeightString) {
- // This should be much faster than using regex to verify input and Integer.parseInt
- return fontWeightString.length() == 3
- && fontWeightString.endsWith("00")
- && fontWeightString.charAt(0) <= '9'
- && fontWeightString.charAt(0) >= '1'
- ? 100 * (fontWeightString.charAt(0) - '0')
- : -1;
- }
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TypefaceStyle.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TypefaceStyle.java
new file mode 100644
index 00000000000..3119ae63afd
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TypefaceStyle.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.views.text;
+
+import android.graphics.Typeface;
+import android.os.Build;
+import com.facebook.infer.annotation.Nullsafe;
+
+/** Responsible for normalizing style and numeric weight for backward compatibility. */
+@Nullsafe(Nullsafe.Mode.LOCAL)
+class TypefaceStyle {
+
+ public static final int BOLD = 700;
+ public static final int NORMAL = 400;
+
+ private static final int MIN_WEIGHT = 1;
+ private static final int MAX_WEIGHT = 1000;
+
+ private final boolean mItalic;
+ private final int mWeight;
+
+ public TypefaceStyle(int weight, boolean italic) {
+ mItalic = italic;
+ mWeight = weight == ReactBaseTextShadowNode.UNSET ? NORMAL : weight;
+ }
+
+ public TypefaceStyle(int style) {
+ if (style == ReactBaseTextShadowNode.UNSET) {
+ style = Typeface.NORMAL;
+ }
+
+ mItalic = (style & Typeface.ITALIC) != 0;
+ mWeight = (style & Typeface.BOLD) != 0 ? BOLD : NORMAL;
+ }
+
+ /**
+ * If `weight` is supplied, it will be combined with the italic bit from `style`. Otherwise, any
+ * existing weight bit in `style` will be used.
+ */
+ public TypefaceStyle(int style, int weight) {
+ if (style == ReactBaseTextShadowNode.UNSET) {
+ style = Typeface.NORMAL;
+ }
+
+ mItalic = (style & Typeface.ITALIC) != 0;
+ mWeight =
+ weight == ReactBaseTextShadowNode.UNSET
+ ? (style & Typeface.BOLD) != 0 ? BOLD : NORMAL
+ : weight;
+ }
+
+ public int getNearestStyle() {
+ if (mWeight < BOLD) {
+ return mItalic ? Typeface.ITALIC : Typeface.NORMAL;
+ } else {
+ return mItalic ? Typeface.BOLD_ITALIC : Typeface.BOLD;
+ }
+ }
+
+ public Typeface apply(Typeface typeface) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ return Typeface.create(typeface, getNearestStyle());
+ } else {
+ return Typeface.create(typeface, mWeight, mItalic);
+ }
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
index ba88f16e633..6aed922399a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
@@ -107,8 +107,8 @@ public class ReactEditText extends AppCompatEditText
private TextAttributes mTextAttributes;
private boolean mTypefaceDirty = false;
private @Nullable String mFontFamily = null;
- private int mFontWeight = ReactTypefaceUtils.UNSET;
- private int mFontStyle = ReactTypefaceUtils.UNSET;
+ private int mFontWeight = UNSET;
+ private int mFontStyle = UNSET;
private boolean mAutoFocus = false;
private boolean mDidAttachToWindow = false;
diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js
index 0847f6c944a..ea9f6b0c982 100644
--- a/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js
+++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js
@@ -200,6 +200,38 @@ exports.examples = ([
);
},
},
+ {
+ title: 'Font Weight',
+ render: function(): React.Node {
+ return (
+
+
+ {[
+ 'normal',
+ 'bold',
+ '900',
+ '800',
+ '700',
+ '600',
+ '500',
+ '400',
+ '300',
+ '200',
+ '100',
+ ].map(fontWeight => (
+
+ ))}
+
+ );
+ },
+ },
{
title: 'Text input, themes and heights',
render: function(): React.Node {