hypertoxin
Version:
A themeable ReactNative component library for developing native apps
420 lines (389 loc) • 15.5 kB
JavaScript
/**
* Copyright 2016-present Tuan Le.
*
* Licensed under the MIT License.
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*------------------------------------------------------------------------
*
* @module SubtitleText
* @description - Subtitle text component.
*
* @author Tuan Le (tuan.t.lei@gmail.com)
*
*
* @flow
*/
; // eslint-disable-line
import React from 'react';
import ReactNative from 'react-native'; // eslint-disable-line
import PropTypes from 'prop-types';
import { Text as AnimatedText } from 'react-native-animatable';
import {
DefaultTheme,
DefaultThemeContext
} from '../../themes/default-theme';
const DEFAULT_ANIMATION_DURATION_MS = 300;
const DEFAULT_SUBTITLE_TEXT_STYLE = DefaultTheme.text.font.subtitle;
const readjustStyle = (newStyle = {
shade: `themed`,
alignment: `left`,
decoration: `none`,
font: `themed`,
indentation: 0,
color: `themed`
}, prevAdjustedStyle = DEFAULT_SUBTITLE_TEXT_STYLE, Theme = DefaultTheme) => {
const {
shade,
alignment,
decoration,
font,
indentation,
color,
style
} = newStyle;
const themedShade = shade === `themed` ? Theme.text.subtitle.shade : shade;
let themedColor;
if (color === `themed`) {
if (Theme.text.color.subtitle.hasOwnProperty(Theme.text.subtitle.color)) {
themedColor = Theme.text.color.subtitle[Theme.text.subtitle.color][themedShade];
} else {
themedColor = Theme.text.subtitle.color;
}
} else if (Theme.text.color.subtitle.hasOwnProperty(color)) {
themedColor = Theme.text.color.subtitle[color][themedShade];
} else {
themedColor = color;
}
return {
small: {
...prevAdjustedStyle.small,
...Theme.text.font.subtitle.small,
fontFamily: font === `themed` ? Theme.text.font.subtitle.small.fontFamily : font,
textAlign: alignment,
textDecorationLine: decoration,
paddingLeft: indentation > 0 ? indentation : 0,
paddingRight: indentation < 0 ? -indentation : 0,
color: themedColor,
...(typeof style === `object` ? style : {})
},
normal: {
...prevAdjustedStyle.normal,
...Theme.text.font.subtitle.normal,
fontFamily: font === `themed` ? Theme.text.font.subtitle.normal.fontFamily : font,
textAlign: alignment,
textDecorationLine: decoration,
paddingLeft: indentation > 0 ? indentation : 0,
paddingRight: indentation < 0 ? -indentation : 0,
color: themedColor,
...(typeof style === `object` ? style : {})
},
large: {
...prevAdjustedStyle.large,
...Theme.text.font.subtitle.large,
fontFamily: font === `themed` ? Theme.text.font.subtitle.large.fontFamily : font,
textAlign: alignment,
textDecorationLine: decoration,
paddingLeft: indentation > 0 ? indentation : 0,
paddingRight: indentation < 0 ? -indentation : 0,
color: themedColor,
...(typeof style === `object` ? style : {})
}
};
};
export default class SubtitleText extends React.Component {
static contextType = DefaultThemeContext
static propTypes = {
exclusions: PropTypes.arrayOf(PropTypes.string),
room: PropTypes.oneOf([
`none`,
`content-left`, `content-middle`, `content-right`,
`content-bottom`, `content-top`,
`media`, `activity-indicator`
]),
shade: PropTypes.oneOf([ `themed`, `light`, `dark` ]),
alignment: PropTypes.oneOf([ `left`, `center`, `right` ]),
decoration: PropTypes.oneOf([ `none`, `underline`, `line-through` ]),
size: PropTypes.oneOf([ `themed`, `small`, `normal`, `large` ]),
font: PropTypes.string,
indentation: PropTypes.number,
uppercased: PropTypes.bool,
lowercased: PropTypes.bool,
color: PropTypes.string,
initialAnimation: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
refName: PropTypes.string,
transitions: PropTypes.arrayOf(PropTypes.shape({
to: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]),
from: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]),
option: PropTypes.shape({
duration: PropTypes.number,
delay: PropTypes.number,
easing: PropTypes.string
})
})),
onTransitionBegin: PropTypes.func,
onTransitionEnd: PropTypes.func,
onAnimationBegin: PropTypes.func,
onAnimationEnd: PropTypes.func
})
])
}
static defaultProps = {
exclusions: [ `` ],
room: `none`,
shade: `themed`,
alignment: `left`,
decoration: `none`,
font: `themed`,
size: `themed`,
indentation: 0,
uppercased: false,
lowercased: false,
color: `themed`,
initialAnimation: `themed`
}
static getDerivedStateFromProps (props, state) {
const {
shade,
alignment,
decoration,
font,
indentation,
color,
style
} = props;
const {
Theme
} = state.context;
return {
adjustedStyle: readjustStyle({
shade,
alignment,
decoration,
font,
indentation,
color,
style
}, state.adjustedStyle, Theme)
};
}
constructor (props) {
super(props);
const component = this;
component.refCache = {};
component.state = {
context: {
Theme: DefaultTheme
},
adjustedStyle: DEFAULT_SUBTITLE_TEXT_STYLE
};
}
animate (animation = {
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) {
const component = this;
const {
Theme
} = component.context;
if (typeof animation === `string` && animation !== `none`) {
const animationName = animation.replace(/-([a-z])/g, (match, word) => word.toUpperCase());
if (Theme.text.animation.subtitle.hasOwnProperty(animationName)) {
animation = Theme.text.animation.subtitle[animationName];
}
}
if (typeof animation === `object`) {
const {
refName,
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
const componentRef = component.refCache[refName];
if (componentRef !== undefined && Array.isArray(transitions)) {
let transitionDuration = 0;
const transitionPromises = transitions.map((transition, transitionIndex) => {
let transitionBeginPromise;
let transitionEndPromise;
if (typeof transition === `object`) {
let transitionType;
let componentRefTransition = {
from: {},
to: {}
};
let componentRefTransitionOption = {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
};
if (transition.hasOwnProperty(`from`)) {
let from = typeof transition.from === `function` ? transition.from(component.props, component.state, component.context) : transition.from;
componentRefTransition.from = typeof from === `object` ? from : {};
transitionType = `from`;
}
if (transition.hasOwnProperty(`to`)) {
let to = typeof transition.to === `function` ? transition.to(component.props, component.state, component.context) : transition.to;
componentRefTransition.to = typeof to === `object` ? to : {};
transitionType = transitionType === `from` ? `from-to` : `to`;
}
if (transition.hasOwnProperty(`option`) && typeof transition.option === `object`) {
componentRefTransitionOption = {
...componentRefTransitionOption,
...transition.option
};
}
transitionBeginPromise = new Promise((resolve) => {
setTimeout(() => {
if (transitionType === `to`) {
componentRef.transitionTo(
componentRefTransition.to,
componentRefTransitionOption.duration,
componentRefTransitionOption.easing,
componentRefTransitionOption.delay
);
} else if (transitionType === `from-to`) {
setTimeout(() => {
componentRef.transition(
componentRefTransition.from,
componentRefTransition.to,
componentRefTransitionOption.duration,
componentRefTransitionOption.easing
);
}, componentRefTransitionOption.delay);
}
(typeof onTransitionBegin === `function` ? onTransitionBegin : () => null)(transitionIndex);
resolve((_onTransitionBegin) => (typeof _onTransitionBegin === `function` ? _onTransitionBegin : () => null)(_onTransitionBegin));
}, transitionDuration + 5);
});
transitionDuration += componentRefTransitionOption.duration + componentRefTransitionOption.delay;
transitionEndPromise = new Promise((resolve) => {
setTimeout(() => {
(typeof onTransitionEnd === `function` ? onTransitionEnd : () => null)(transitionIndex);
resolve((_onTransitionEnd) => (typeof _onTransitionEnd === `function` ? _onTransitionEnd : () => null)(transitionIndex));
}, transitionDuration);
});
}
return [ transitionBeginPromise, transitionEndPromise ];
});
const animationBeginPromise = new Promise((resolve) => {
(typeof onAnimationBegin === `function` ? onAnimationBegin : () => null)();
resolve((_onAnimationBegin) => (typeof _onAnimationBegin === `function` ? _onAnimationBegin : () => null)());
});
const animationEndPromise = new Promise((resolve) => {
setTimeout(() => {
(typeof onAnimationEnd === `function` ? onAnimationEnd : () => null)();
resolve((_onAnimationEnd) => (typeof _onAnimationEnd === `function` ? _onAnimationEnd : () => null)());
}, transitionDuration + 5);
});
return Promise.all([
animationBeginPromise,
...transitionPromises.flat(),
animationEndPromise
].filter((animationPromise) => animationPromise !== undefined));
}
}
}
componentDidMount () {
const component = this;
const {
shade,
alignment,
decoration,
font,
indentation,
color,
initialAnimation,
style
} = component.props;
const {
Theme
} = component.context;
component.setState((prevState) => {
return {
context: {
Theme
},
adjustedStyle: readjustStyle({
shade,
alignment,
decoration,
font,
indentation,
color,
style
}, prevState.adjustedStyle, Theme)
};
}, () => {
if ((typeof initialAnimation === `string` && initialAnimation !== `none`) || typeof initialAnimation === `object`) {
component.animate(initialAnimation);
}
});
}
componentWillUnMount () {
const component = this;
component.refCache = {};
}
render () {
const component = this;
const {
size,
uppercased,
lowercased,
children
} = component.props;
const {
adjustedStyle
} = component.state;
const {
Theme
} = component.context;
const themedSize = size === `themed` ? Theme.text.subtitle.size : size;
let contentChildren = null;
if (React.Children.count(children) > 0) {
contentChildren = React.Children.toArray(React.Children.map(children, (child) => {
if (uppercased) {
return child.toUpperCase();
} else if (lowercased) {
return child.toLowerCase();
}
return child;
}));
}
return (
<AnimatedText
ref = {(componentRef) => {
component.refCache[`animated-text`] = componentRef;
}}
style = { adjustedStyle[themedSize] }
ellipsizeMode = 'tail'
numberOfLines = { 1 }
useNativeDriver = { true }
>
{
contentChildren
}
</AnimatedText>
);
}
}