From 6416166bc3c40cb6a48c99f9fe270b0b459056c3 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Wed, 27 Jul 2016 15:06:41 -0700 Subject: [PATCH] [add] support for RTL layout Add `I18nManager` API from React Native. This can be used to control when the app displays in RTL mode. Add `$noI18n` property suffix for properties that StyleSheet will automatically flip. This can be used to opt-out of automatic flipping on a per-declaration basis. --- README.md | 1 + docs/apis/I18nManager.md | 27 ++++ docs/apis/NetInfo.md | 2 +- docs/components/Text.md | 14 +- docs/components/View.md | 39 ++++-- examples/Text/TextExample.js | 2 +- src/apis/I18nManager/__tests__/index-test.js | 38 ++++++ src/apis/I18nManager/index.js | 33 +++++ src/apis/StyleSheet/StyleSheetValidation.js | 1 - .../StyleSheet/__tests__/i18nStyle-test.js | 91 +++++++++++++ src/apis/StyleSheet/createReactStyleObject.js | 7 +- src/apis/StyleSheet/i18nStyle.js | 124 ++++++++++++++++++ src/components/Text/TextStylePropTypes.js | 29 +--- src/core.js | 2 + src/index.js | 2 + src/propTypes/BorderPropTypes.js | 11 +- src/propTypes/LayoutPropTypes.js | 11 +- src/propTypes/TextPropTypes.js | 45 +++++++ 18 files changed, 432 insertions(+), 47 deletions(-) create mode 100644 docs/apis/I18nManager.md create mode 100644 src/apis/I18nManager/__tests__/index-test.js create mode 100644 src/apis/I18nManager/index.js create mode 100644 src/apis/StyleSheet/__tests__/i18nStyle-test.js create mode 100644 src/apis/StyleSheet/i18nStyle.js create mode 100644 src/propTypes/TextPropTypes.js diff --git a/README.md b/README.md index aac20768..f6496a97 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Exported modules: * [`AppState`](docs/apis/AppState.md) * [`AsyncStorage`](docs/apis/AsyncStorage.md) * [`Dimensions`](docs/apis/Dimensions.md) + * [`I18nManager`](docs/apis/I18nManager.md) * [`NativeMethods`](docs/apis/NativeMethods.md) * [`NetInfo`](docs/apis/NetInfo.md) * [`PanResponder`](http://facebook.github.io/react-native/releases/0.20/docs/panresponder.html#content) (mirrors React Native) diff --git a/docs/apis/I18nManager.md b/docs/apis/I18nManager.md new file mode 100644 index 00000000..9258bda6 --- /dev/null +++ b/docs/apis/I18nManager.md @@ -0,0 +1,27 @@ +# I18nManager + +Control and set the layout and writing direction of the application. You must +set `dir="rtl"` (and should set `lang="${lang}"`) on the root element of your +app. + +## Properties + +**isRTL**: bool = false + +Whether the application is currently in RTL mode. + +## Methods + +static **allowRTL**(allowRTL: bool) + +Allow the application to display in RTL mode. + +static **forceRTL**(allowRTL: bool) + +Force the application to display in RTL mode. + +static **setRTL**(allowRTL: bool) + +Set the application to display in RTL mode. You will need to determine the +user's preferred locale and if it is an RTL language. (This is best done on the +server as it is notoriously inaccurate to deduce client-side.) diff --git a/docs/apis/NetInfo.md b/docs/apis/NetInfo.md index 7a8655ab..a2fdf7f4 100644 --- a/docs/apis/NetInfo.md +++ b/docs/apis/NetInfo.md @@ -29,7 +29,7 @@ static **removeEventListener**(eventName: ChangeEventName, handler: Function) ## Properties -**isConnected** +**isConnected**: bool = true Available on all user agents. Asynchronously fetch a boolean to determine internet connectivity. diff --git a/docs/components/Text.md b/docs/components/Text.md index 13ae37a8..06cd3631 100644 --- a/docs/components/Text.md +++ b/docs/components/Text.md @@ -68,14 +68,22 @@ Lets the user select the text. + `fontWeight` + `letterSpacing` + `lineHeight` -+ `textAlign` ++ `textAlign`‡ + `textAlignVertical` + `textDecorationLine` -+ `textShadow` ++ `textOverflow` ++ `textRendering` ++ `textShadowColor` ++ `textShadowOffset`‡ ++ `textShadowRadius` + `textTransform` ++ `unicodeBidi` + `whiteSpace` + `wordWrap` -+ `writingDirection` ++ `writingDirection`‡ + +‡ This property can be suffixed with `$noI18n` to prevent automatic +bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`. **testID**: string diff --git a/docs/components/View.md b/docs/components/View.md index c5120fa2..3bbdaf89 100644 --- a/docs/components/View.md +++ b/docs/components/View.md @@ -108,10 +108,26 @@ from `style`. + `backgroundPosition` + `backgroundRepeat` + `backgroundSize` -+ `borderColor` -+ `borderRadius` -+ `borderStyle` -+ `borderWidth` ++ `borderColor` (single value) ++ `borderTopColor` ++ `borderBottomColor` ++ `borderRightColor`‡ ++ `borderLeftColor`‡ ++ `borderRadius` (single value) ++ `borderTopLeftRadius`‡ ++ `borderTopRightRadius`‡ ++ `borderBottomLeftRadius`‡ ++ `borderBottomRightRadius`‡ ++ `borderStyle` (single value) ++ `borderTopStyle` ++ `borderRightStyle`‡ ++ `borderBottomStyle` ++ `borderLeftStyle`‡ ++ `borderWidth` (single value) ++ `borderBottomWidth` ++ `borderLeftWidth`‡ ++ `borderRightWidth`‡ ++ `borderTopWidth` + `bottom` + `boxShadow` + `boxSizing` @@ -124,12 +140,12 @@ from `style`. + `flexWrap` + `height` + `justifyContent` -+ `left` ++ `left`‡ + `margin` (single value) + `marginBottom` + `marginHorizontal` -+ `marginLeft` -+ `marginRight` ++ `marginLeft`‡ ++ `marginRight`‡ + `marginTop` + `marginVertical` + `maxHeight` @@ -144,12 +160,12 @@ from `style`. + `padding` (single value) + `paddingBottom` + `paddingHorizontal` -+ `paddingLeft` -+ `paddingRight` ++ `paddingLeft`‡ ++ `paddingRight`‡ + `paddingTop` + `paddingVertical` + `position` -+ `right` ++ `right`‡ + `top` + `transform` + `transformMatrix` @@ -158,6 +174,9 @@ from `style`. + `width` + `zIndex` +‡ This property can be suffixed with `$noI18n` to prevent automatic +bidi-flipping in RTL mode. This is only supported if `Platform.OS === 'web'`. + Default: ```js diff --git a/examples/Text/TextExample.js b/examples/Text/TextExample.js index 1f88736f..f6f41e80 100644 --- a/examples/Text/TextExample.js +++ b/examples/Text/TextExample.js @@ -271,7 +271,7 @@ const examples = [ auto (default) - english LTR - + أحب اللغة العربية auto (default) - arabic RTL diff --git a/src/apis/I18nManager/__tests__/index-test.js b/src/apis/I18nManager/__tests__/index-test.js new file mode 100644 index 00000000..f2de7590 --- /dev/null +++ b/src/apis/I18nManager/__tests__/index-test.js @@ -0,0 +1,38 @@ +/* eslint-env mocha */ + +import assert from 'assert' +import I18nManager from '..' + +suite('apis/I18nManager', () => { + suite('when RTL not enabled', () => { + setup(() => { + I18nManager.setRTL(false) + }) + + test('is "false" by default', () => { + assert.equal(I18nManager.isRTL, false) + }) + + test('is "true" when forced', () => { + I18nManager.forceRTL(true) + assert.equal(I18nManager.isRTL, true) + I18nManager.forceRTL(false) + }) + }) + + suite('when RTL is enabled', () => { + setup(() => { + I18nManager.setRTL(true) + }) + + test('is "true" by default', () => { + assert.equal(I18nManager.isRTL, true) + }) + + test('is "false" when not allowed', () => { + I18nManager.allowRTL(false) + assert.equal(I18nManager.isRTL, false) + I18nManager.allowRTL(true) + }) + }) +}) diff --git a/src/apis/I18nManager/index.js b/src/apis/I18nManager/index.js new file mode 100644 index 00000000..f454c34b --- /dev/null +++ b/src/apis/I18nManager/index.js @@ -0,0 +1,33 @@ +type I18nManagerStatus = { + allowRTL: (allowRTL: boolean) => {}, + forceRTL: (forceRTL: boolean) => {}, + setRTL: (setRTL: boolean) => {}, + isRTL: boolean +} + +let isApplicationLanguageRTL = false +let isRTLAllowed = true +let isRTLForced = false + +const I18nManager: I18nManagerStatus = { + allowRTL(bool) { + isRTLAllowed = bool + }, + forceRTL(bool) { + isRTLForced = bool + }, + setRTL(bool) { + isApplicationLanguageRTL = bool + }, + get isRTL() { + if (isRTLForced) { + return true + } + if (isRTLAllowed && isApplicationLanguageRTL) { + return true + } + return false + } +} + +module.exports = I18nManager diff --git a/src/apis/StyleSheet/StyleSheetValidation.js b/src/apis/StyleSheet/StyleSheetValidation.js index 307ad574..58dd317f 100644 --- a/src/apis/StyleSheet/StyleSheetValidation.js +++ b/src/apis/StyleSheet/StyleSheetValidation.js @@ -61,7 +61,6 @@ StyleSheetValidation.addValidStylePropTypes({ clear: PropTypes.string, cursor: PropTypes.string, display: PropTypes.string, - direction: PropTypes.string, /* @private */ float: PropTypes.oneOf([ 'left', 'none', 'right' ]), font: PropTypes.string, /* @private */ listStyle: PropTypes.string diff --git a/src/apis/StyleSheet/__tests__/i18nStyle-test.js b/src/apis/StyleSheet/__tests__/i18nStyle-test.js new file mode 100644 index 00000000..058dab68 --- /dev/null +++ b/src/apis/StyleSheet/__tests__/i18nStyle-test.js @@ -0,0 +1,91 @@ +/* eslint-env mocha */ + +import assert from 'assert' +import I18nManager from '../../I18nManager' +import i18nStyle from '../i18nStyle' + +const initial = { + borderLeftColor: 'red', + borderRightColor: 'blue', + borderTopLeftRadius: 10, + borderTopRightRadius: '1rem', + borderBottomLeftRadius: 20, + borderBottomRightRadius: '2rem', + borderLeftStyle: 'solid', + borderRightStyle: 'dotted', + borderLeftWidth: 5, + borderRightWidth: 6, + left: 1, + marginLeft: 7, + marginRight: 8, + paddingLeft: 9, + paddingRight: 10, + right: 2, + textAlign: 'left', + textShadowOffset: { width: '1rem', height: 10 }, + writingDirection: 'ltr' +} + +const initialNoI18n = Object.keys(initial).reduce((acc, prop) => { + const newProp = `${prop}$noI18n` + acc[newProp] = initial[prop] + return acc +}, {}) + +const expected = { + borderLeftColor: 'blue', + borderRightColor: 'red', + borderTopLeftRadius: '1rem', + borderTopRightRadius: 10, + borderBottomLeftRadius: '2rem', + borderBottomRightRadius: 20, + borderLeftStyle: 'dotted', + borderRightStyle: 'solid', + borderLeftWidth: 6, + borderRightWidth: 5, + left: 2, + marginLeft: 8, + marginRight: 7, + paddingLeft: 10, + paddingRight: 9, + right: 1, + textAlign: 'right', + textShadowOffset: { width: '-1rem', height: 10 }, + writingDirection: 'rtl' +} + +suite('apis/StyleSheet/i18nStyle', () => { + suite('LTR mode', () => { + setup(() => { + I18nManager.allowRTL(false) + }) + + teardown(() => { + I18nManager.allowRTL(true) + }) + + test('does not auto-flip', () => { + assert.deepEqual(i18nStyle(initial), initial) + }) + test('normalizes properties', () => { + assert.deepEqual(i18nStyle(initialNoI18n), initial) + }) + }) + + suite('RTL mode', () => { + setup(() => { + I18nManager.forceRTL(true) + }) + + teardown(() => { + I18nManager.forceRTL(false) + }) + + test('does auto-flip', () => { + assert.deepEqual(i18nStyle(initial), expected) + }) + test('normalizes properties', () => { + assert.deepEqual(i18nStyle(initialNoI18n), initial) + }) + }) +}) diff --git a/src/apis/StyleSheet/createReactStyleObject.js b/src/apis/StyleSheet/createReactStyleObject.js index ce8d99b6..1657a6f6 100644 --- a/src/apis/StyleSheet/createReactStyleObject.js +++ b/src/apis/StyleSheet/createReactStyleObject.js @@ -1,5 +1,6 @@ import expandStyle from './expandStyle' import flattenStyle from '../../modules/flattenStyle' +import i18nStyle from './i18nStyle' import processTextShadow from './processTextShadow' import processTransform from './processTransform' import processVendorPrefixes from './processVendorPrefixes' @@ -10,8 +11,10 @@ const plugins = [ processVendorPrefixes ] -const applyPlugins = (style) => plugins.reduce((style, plugin) => plugin(style), style) +const applyPlugins = (style) => { + return plugins.reduce((style, plugin) => plugin(style), style) +} -const createReactDOMStyleObject = (reactNativeStyle) => applyPlugins(expandStyle(flattenStyle(reactNativeStyle))) +const createReactDOMStyleObject = (reactNativeStyle) => applyPlugins(expandStyle(i18nStyle(flattenStyle(reactNativeStyle)))) module.exports = createReactDOMStyleObject diff --git a/src/apis/StyleSheet/i18nStyle.js b/src/apis/StyleSheet/i18nStyle.js new file mode 100644 index 00000000..3ab00fc7 --- /dev/null +++ b/src/apis/StyleSheet/i18nStyle.js @@ -0,0 +1,124 @@ +import I18nManager from '../I18nManager' + +const CSS_UNIT_RE = /^[+-]?\d*(?:\.\d+)?(?:[Ee][+-]?\d+)?(\w*)/ + +/** + * Map of property names to their BiDi equivalent. + */ +const PROPERTIES_TO_SWAP = { + 'borderTopLeftRadius': 'borderTopRightRadius', + 'borderTopRightRadius': 'borderTopLeftRadius', + 'borderBottomLeftRadius': 'borderBottomRightRadius', + 'borderBottomRightRadius': 'borderBottomLeftRadius', + 'borderLeftColor': 'borderRightColor', + 'borderLeftStyle': 'borderRightStyle', + 'borderLeftWidth': 'borderRightWidth', + 'borderRightColor': 'borderLeftColor', + 'borderRightWidth': 'borderLeftWidth', + 'borderRightStyle': 'borderLeftStyle', + 'left': 'right', + 'marginLeft': 'marginRight', + 'marginRight': 'marginLeft', + 'paddingLeft': 'paddingRight', + 'paddingRight': 'paddingLeft', + 'right': 'left' +} + +const PROPERTIES_SWAP_LEFT_RIGHT = { + 'clear': true, + 'float': true, + 'textAlign': true +} + +const PROPERTIES_SWAP_LTR_RTL = { + 'writingDirection': true +} + +/** + * Invert the sign of a numeric-like value + */ +const additiveInverse = (value: String | Number) => { + if (typeof value === 'string') { + const number = parseFloat(value, 10) * -1 + const unit = getUnit(value) + return `${number}${unit}` + } else if (isNumeric(value)) { + return value * -1 + } +} + +/** + * BiDi flip the given property. + */ +const flipProperty = (prop:String): String => { + return PROPERTIES_TO_SWAP.hasOwnProperty(prop) ? PROPERTIES_TO_SWAP[prop] : prop +} + +/** + * BiDi flip translateX + */ +const flipTransform = (transform: Object): Object => { + const translateX = transform.translateX + if (translateX != null) { + transform.translateX = additiveInverse(translateX) + } + return transform +} + +/** + * Get the CSS unit for string values + */ +const getUnit = (str) => str.match(CSS_UNIT_RE)[1] + +const isNumeric = (n) => { + return !isNaN(parseFloat(n)) && isFinite(n) +} + +const swapLeftRight = (value:String): String => { + return value === 'left' ? 'right' : value === 'right' ? 'left' : value +} + +const swapLtrRtl = (value:String): String => { + return value === 'ltr' ? 'rtl' : value === 'rtl' ? 'ltr' : value +} + +const i18nStyle = (style = {}) => { + const newStyle = {} + for (const prop in style) { + if (style.hasOwnProperty(prop)) { + const indexOfNoFlip = prop.indexOf('$noI18n') + + if (I18nManager.isRTL) { + if (PROPERTIES_TO_SWAP[prop]) { + const newProp = flipProperty(prop) + newStyle[newProp] = style[prop] + } else if (PROPERTIES_SWAP_LEFT_RIGHT[prop]) { + newStyle[prop] = swapLeftRight(style[prop]) + } else if (PROPERTIES_SWAP_LTR_RTL[prop]) { + newStyle[prop] = swapLtrRtl(style[prop]) + } else if (prop === 'textShadowOffset') { + newStyle[prop] = style[prop] + newStyle[prop].width = additiveInverse(style[prop].width) + } else if (prop === 'transform') { + newStyle[prop] = style[prop].map(flipTransform) + } else if (indexOfNoFlip > -1) { + const newProp = prop.substring(0, indexOfNoFlip) + newStyle[newProp] = style[prop] + } else { + newStyle[prop] = style[prop] + } + } else { + if (indexOfNoFlip > -1) { + const newProp = prop.substring(0, indexOfNoFlip) + newStyle[newProp] = style[prop] + } else { + newStyle[prop] = style[prop] + } + } + } + } + + return newStyle +} + +module.exports = i18nStyle diff --git a/src/components/Text/TextStylePropTypes.js b/src/components/Text/TextStylePropTypes.js index b6c0c2f2..75631f4f 100644 --- a/src/components/Text/TextStylePropTypes.js +++ b/src/components/Text/TextStylePropTypes.js @@ -1,32 +1,7 @@ -import { PropTypes } from 'react' -import ColorPropType from '../../propTypes/ColorPropType' +import TextPropTypes from '../../propTypes/TextPropTypes' import ViewStylePropTypes from '../View/ViewStylePropTypes' -const { number, oneOf, oneOfType, shape, string } = PropTypes -const numberOrString = oneOfType([ number, string ]) - module.exports = { ...ViewStylePropTypes, - color: ColorPropType, - fontFamily: string, - fontSize: numberOrString, - fontStyle: string, - fontWeight: string, - letterSpacing: numberOrString, - lineHeight: numberOrString, - textAlign: oneOf([ 'center', 'inherit', 'justify', 'justify-all', 'left', 'right' ]), - textAlignVertical: oneOf([ 'auto', 'bottom', 'center', 'top' ]), - textDecorationLine: string, - /* @platform web */ - textOverflow: string, - textShadowColor: ColorPropType, - textShadowOffset: shape({ width: number, height: number }), - textShadowRadius: number, - /* @platform web */ - textTransform: oneOf([ 'capitalize', 'lowercase', 'none', 'uppercase' ]), - /* @platform web */ - whiteSpace: string, - /* @platform web */ - wordWrap: string, - writingDirection: oneOf([ 'auto', 'ltr', 'rtl' ]) + ...TextPropTypes } diff --git a/src/core.js b/src/core.js index 1100e200..8c7d1e5e 100644 --- a/src/core.js +++ b/src/core.js @@ -3,6 +3,7 @@ import './modules/injectResponderEventPlugin' import findNodeHandle from './modules/findNodeHandle' import ReactDOM from 'react-dom' import ReactDOMServer from 'react-dom/server' +import I18nManager from './apis/I18nManager' import StyleSheet from './apis/StyleSheet' import Image from './components/Image' import Text from './components/Text' @@ -15,6 +16,7 @@ const ReactNativeCore = { renderToStaticMarkup: ReactDOMServer.renderToStaticMarkup, renderToString: ReactDOMServer.renderToString, unmountComponentAtNode: ReactDOM.unmountComponentAtNode, + I18nManager, StyleSheet, Image, Text, diff --git a/src/index.js b/src/index.js index 27ece9dc..8155ca92 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,7 @@ import AppState from './apis/AppState' import AsyncStorage from './apis/AsyncStorage' import Dimensions from './apis/Dimensions' import Easing from 'animated/lib/Easing' +import I18nManager from './apis/I18nManager' import InteractionManager from './apis/InteractionManager' import NetInfo from './apis/NetInfo' import PanResponder from './apis/PanResponder' @@ -59,6 +60,7 @@ const ReactNative = { AsyncStorage, Dimensions, Easing, + I18nManager, InteractionManager, NetInfo, PanResponder, diff --git a/src/propTypes/BorderPropTypes.js b/src/propTypes/BorderPropTypes.js index 238cc1e5..8d99a5d7 100644 --- a/src/propTypes/BorderPropTypes.js +++ b/src/propTypes/BorderPropTypes.js @@ -19,7 +19,16 @@ const BorderPropTypes = { borderTopStyle: BorderStylePropType, borderRightStyle: BorderStylePropType, borderBottomStyle: BorderStylePropType, - borderLeftStyle: BorderStylePropType + borderLeftStyle: BorderStylePropType, + /* Props to opt-out of RTL flipping */ + borderLeftColor$noI18n: ColorPropType, + borderRightColor$noI18n: ColorPropType, + borderTopLeftRadius$noI18n: numberOrString, + borderTopRightRadius$noI18n: numberOrString, + borderBottomLeftRadius$noI18n: numberOrString, + borderBottomRightRadius$noI18n: numberOrString, + borderLeftStyle$noI18n: BorderStylePropType, + borderRightStyle$noI18n: BorderStylePropType } module.exports = BorderPropTypes diff --git a/src/propTypes/LayoutPropTypes.js b/src/propTypes/LayoutPropTypes.js index d83f2d09..5f46ed53 100644 --- a/src/propTypes/LayoutPropTypes.js +++ b/src/propTypes/LayoutPropTypes.js @@ -48,7 +48,16 @@ const LayoutPropTypes = { left: numberOrString, position: oneOf([ 'absolute', 'fixed', 'relative', 'static' ]), right: numberOrString, - top: numberOrString + top: numberOrString, + // opt-out of RTL flipping + borderLeftWidth$noI18n: numberOrString, + borderRightWidth$noI18n: numberOrString, + left$noI18n: numberOrString, + marginLeft$noI18n: numberOrString, + marginRight$noI18n: numberOrString, + paddingLeft$noI18n: numberOrString, + paddingRight$noI18n: numberOrString, + right$noI18n: numberOrString } module.exports = LayoutPropTypes diff --git a/src/propTypes/TextPropTypes.js b/src/propTypes/TextPropTypes.js new file mode 100644 index 00000000..8bc969ee --- /dev/null +++ b/src/propTypes/TextPropTypes.js @@ -0,0 +1,45 @@ +import ColorPropType from './ColorPropType' +import { PropTypes } from 'react' + +const { number, oneOf, oneOfType, shape, string } = PropTypes +const numberOrString = oneOfType([ number, string ]) + +const ShadowOffsetPropType = shape({ width: number, height: number }) +const TextAlignPropType = oneOf([ 'center', 'inherit', 'justify', 'justify-all', 'left', 'right' ]) +const WritingDirectionPropType = oneOf([ 'auto', 'ltr', 'rtl' ]) + +const TextPropTypes = { + // box model + color: ColorPropType, + fontFamily: string, + fontSize: numberOrString, + fontStyle: string, + fontWeight: string, + letterSpacing: numberOrString, + lineHeight: numberOrString, + textAlign: TextAlignPropType, + textAlignVertical: oneOf([ 'auto', 'bottom', 'center', 'top' ]), + textDecorationLine: string, + /* @platform web */ + textOverflow: string, + /* @platform web */ + textRendering: oneOf([ 'auto', 'geometricPrecision', 'optimizeLegibility', 'optimizeSpeed' ]), + textShadowColor: ColorPropType, + textShadowOffset: ShadowOffsetPropType, + textShadowRadius: number, + /* @platform web */ + textTransform: oneOf([ 'capitalize', 'lowercase', 'none', 'uppercase' ]), + /* @platform web */ + unicodeBidi: oneOf([ 'normal', 'bidi-override', 'embed', 'isolate', 'isolate-override', 'plaintext' ]), + /* @platform web */ + whiteSpace: string, + /* @platform web */ + wordWrap: string, + writingDirection: WritingDirectionPropType, + // opt-out of RTL flipping + textAlign$noI18n: TextAlignPropType, + textShadowOffset$noI18n: ShadowOffsetPropType, + writingDirection$noI18n: WritingDirectionPropType +} + +module.exports = TextPropTypes