Skip to content

Commit

Permalink
feat(ui): RTL support
Browse files Browse the repository at this point in the history
  • Loading branch information
sijav authored and artyorsh committed Aug 19, 2019
1 parent c72f7f6 commit 14bf854
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 43 deletions.
8 changes: 6 additions & 2 deletions src/framework/ui/input/input.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,9 +385,13 @@ const styles = StyleSheet.create({
},
placeholder: {},
icon: {},
label: {},
label: {
textAlign: 'left',
},
captionIcon: {},
captionLabel: {},
captionLabel: {
textAlign: 'left',
},
});

export const Input = styled<InputProps>(InputComponent);
8 changes: 6 additions & 2 deletions src/framework/ui/list/listItem.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,12 @@ const styles = StyleSheet.create({
flex: 1,
},
icon: {},
title: {},
description: {},
title: {
textAlign: 'left',
},
description: {
textAlign: 'left',
},
accessory: {},
});

Expand Down
14 changes: 10 additions & 4 deletions src/framework/ui/popover/popoverView.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
PopoverPlacements,
} from './type';
import { Arrow } from '../support/components';
import { I18nLayoutService } from '../support/services';

interface ComponentProps {
placement?: string | PopoverPlacement;
Expand Down Expand Up @@ -55,16 +56,18 @@ export class PopoverView extends React.Component<PopoverViewProps> {

// Translate indicator by passed `indicatorOffset`
// Reverse if needed

let indicatorTranslate: number = isVertical ? -this.props.indicatorOffset : this.props.indicatorOffset;
indicatorTranslate = isReverse ? -indicatorTranslate : indicatorTranslate;
const i18nVerticalIndicatorTranslate = I18nLayoutService.select(indicatorTranslate, -indicatorTranslate);
indicatorTranslate = isVertical ? i18nVerticalIndicatorTranslate : indicatorTranslate;

const containerStyle: ViewStyle = {
const containerStyle: ViewStyle = I18nLayoutService.toI18nStyle({
flexDirection: direction,
alignItems: alignment,
transform: [
{ translateX: containerTranslate },
],
};
});

const contentStyle: ViewStyle = {
backgroundColor: 'black',
Expand All @@ -88,7 +91,10 @@ export class PopoverView extends React.Component<PopoverViewProps> {
};

return {
container: containerStyle,
container: {
...containerStyle,
alignItems: alignment,
},
content: contentStyle,
indicator: indicatorStyle,
};
Expand Down
42 changes: 36 additions & 6 deletions src/framework/ui/popover/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
StyleProp,
StyleSheet,
} from 'react-native';
import { I18nLayoutService } from '../support/services';

export class Point {

Expand Down Expand Up @@ -285,8 +286,13 @@ export class PopoverPlacements {
frame(options: PlacementOptions): Frame {
const { origin, size } = options.source.leftOf(options.other).centerVerticalOf(options.other);

return new Frame(
const x: number = I18nLayoutService.select(
origin.x + options.offsets.left,
options.bounds.size.width - size.width - (origin.x + options.offsets.right),
);

return new Frame(
x,
origin.y,
size.width,
size.height,
Expand Down Expand Up @@ -317,7 +323,7 @@ export class PopoverPlacements {
}

fits(frame: Frame, other: Frame): boolean {
return fitsLeft(frame, other) && fitsTop(frame, other) && fitsBottom(frame, other);
return fitsStart(frame, other) && fitsTop(frame, other) && fitsBottom(frame, other);
}
};

Expand Down Expand Up @@ -403,8 +409,14 @@ export class PopoverPlacements {
frame(options: PlacementOptions): Frame {
const { origin, size } = options.source.topOf(options.other).centerHorizontalOf(options.other);

return new Frame(

const x: number = I18nLayoutService.select(
origin.x,
options.bounds.size.width - (origin.x + size.width),
);

return new Frame(
x,
origin.y + options.offsets.top,
size.width,
size.height,
Expand Down Expand Up @@ -521,8 +533,13 @@ export class PopoverPlacements {
frame(options: PlacementOptions): Frame {
const { origin, size } = options.source.rightOf(options.other).centerVerticalOf(options.other);

return new Frame(
const x: number = I18nLayoutService.select(
origin.x - options.offsets.right,
options.bounds.size.width - size.width - (origin.x - options.offsets.right),
);

return new Frame(
x,
origin.y,
size.width,
size.height,
Expand Down Expand Up @@ -553,7 +570,7 @@ export class PopoverPlacements {
}

fits(frame: Frame, other: Frame): boolean {
return fitsRight(frame, other) && fitsTop(frame, other) && fitsBottom(frame, other);
return fitsEnd(frame, other) && fitsTop(frame, other) && fitsBottom(frame, other);
}
};

Expand Down Expand Up @@ -639,8 +656,13 @@ export class PopoverPlacements {
frame(options: PlacementOptions): Frame {
const { origin, size } = options.source.bottomOf(options.other).centerHorizontalOf(options.other);

return new Frame(
const x: number = I18nLayoutService.select(
origin.x,
options.bounds.size.width - (origin.x + size.width),
);

return new Frame(
x,
origin.y - options.offsets.bottom,
size.width,
size.height,
Expand Down Expand Up @@ -793,6 +815,14 @@ export class PopoverPlacements {
}
}

const fitsStart = (frame: Frame, other: Frame): boolean => {
return I18nLayoutService.select(fitsLeft, fitsRight)(frame, other);
};

const fitsEnd = (frame: Frame, other: Frame): boolean => {
return I18nLayoutService.select(fitsRight, fitsLeft)(frame, other);
};

const fitsLeft = (frame: Frame, other: Frame): boolean => {
return frame.origin.x >= other.origin.x;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ViewProps,
} from 'react-native';
import { StyleType } from '@kitten/theme';
import { I18nLayoutService } from '../../services';

interface ComponentProps {
isAnimated?: boolean;
Expand Down Expand Up @@ -58,24 +59,25 @@ export class CheckMark extends React.Component<CheckMarkProps> {

return (
<Component style={[container, styles.container]}>
<Component style={[shape, left, styles.shape, styles.left]}/>
<Component style={[shape, right, styles.shape, styles.right]}/>
<Component style={[shape, left, styles.shape, styles.left]} />
<Component style={[shape, right, styles.shape, styles.right]} />
</Component>
);
}
}

const styles = StyleSheet.create({
container: {
container: I18nLayoutService.toI18nStyle({
flexDirection: 'row',
transform: [{ rotate: '-5deg' }],
},
}),
shape: {
position: 'absolute',
},
left: {
transform: [{ rotate: '-40deg' }, { translateY: 1 }],
transform: [{ rotate: '-40deg' }],
},
right: {
transform: [{ rotate: '40deg' }, { translateY: 1 }],
transform: [{ rotate: '40deg' }],
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ViewProps,
ViewStyle,
} from 'react-native';
import { I18nLayoutService } from '../../services';

interface ComponentProps {
positions: number;
Expand All @@ -23,8 +24,8 @@ export class TabIndicator extends React.Component<TabIndicatorProps> {
animationDuration: 200,
};

private contentOffset: Animated.Value = new Animated.Value(0);
private indicatorWidth: number;
private contentOffset: Animated.Value = new Animated.Value(0);

public componentDidMount() {
this.contentOffset.addListener(this.onContentOffsetAnimationStateChanged);
Expand All @@ -37,10 +38,7 @@ export class TabIndicator extends React.Component<TabIndicatorProps> {
public componentDidUpdate() {
const { selectedPosition: index } = this.props;

this.scrollToIndex({
index,
animated: true,
});
this.scrollToIndex({ index, animated: true });
}

public componentWillUnmount() {
Expand Down Expand Up @@ -86,7 +84,7 @@ export class TabIndicator extends React.Component<TabIndicatorProps> {
const animationDuration: number = params.animated ? this.props.animationDuration : 0;

return Animated.timing(this.contentOffset, {
toValue: params.offset,
toValue: I18nLayoutService.select(params.offset, -params.offset),
duration: animationDuration,
easing: Easing.linear,
});
Expand Down
41 changes: 41 additions & 0 deletions src/framework/ui/support/services/i18n/i18nLayout.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
I18nManager,
ViewStyle,
} from 'react-native';
import { I18nLayoutFlexMap } from './i18nLayoutFlexMap';
import { I18nLayoutServiceType } from './type';

class NativeI18nLayoutService implements I18nLayoutServiceType {

public isRTL(): boolean {
return I18nManager.isRTL;
}

public select<T>(ltr: T, rtl): T {
return this.isRTL() ? rtl : ltr;
}

/**
* Iterates through I18nLayoutFlexMap and reverses style values if needed.
*
* @param {ViewStyle} source - style to convert
* @param {boolean} rtl - is layout currently in RTL mode (Needed for tests, because unable to mock this)
*
* @returns {ViewStyle} - style reversed to fit i18n
*/
public toI18nStyle(source: ViewStyle, rtl: boolean = this.isRTL()): ViewStyle {
const i18nStyle: ViewStyle = Object.keys(I18nLayoutFlexMap).reduce((style: ViewStyle, prop: string): ViewStyle => {
const currentStyleValue = source[prop];
if (currentStyleValue) {
const i18nStyleValue = I18nLayoutFlexMap[prop].toI18n(currentStyleValue, rtl);
return { ...style, [prop]: i18nStyleValue };
}

return style;
}, {});

return { ...source, ...i18nStyle };
}
}

export const I18nLayoutService = new NativeI18nLayoutService();
60 changes: 60 additions & 0 deletions src/framework/ui/support/services/i18n/i18nLayout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ViewStyle } from 'react-native';
import { I18nLayoutService } from './i18nLayout.service';

describe('@i18n-layout: service checks', () => {

it('* creates LTR style properly', () => {
const i18nStyle: ViewStyle = I18nLayoutService.toI18nStyle({
alignContent: 'flex-start',
alignItems: 'flex-end',
alignSelf: 'flex-start',
justifyContent: 'flex-end',
flexDirection: 'row',
flexWrap: 'wrap-reverse',
}, false);

expect(i18nStyle).toEqual({
alignContent: 'flex-start',
alignItems: 'flex-end',
alignSelf: 'flex-start',
justifyContent: 'flex-end',
flexDirection: 'row',
flexWrap: 'wrap-reverse',
});
});

it('* creates RTL style properly', () => {
const i18nStyle: ViewStyle = I18nLayoutService.toI18nStyle({
alignContent: 'flex-start',
alignItems: 'flex-end',
alignSelf: 'flex-start',
justifyContent: 'flex-end',
flexDirection: 'row',
flexWrap: 'wrap-reverse',
}, true);

expect(i18nStyle).toEqual({
alignContent: 'flex-end',
alignItems: 'flex-start',
alignSelf: 'flex-end',
justifyContent: 'flex-start',
flexDirection: 'row-reverse',
flexWrap: 'wrap',
});
});

it('* creates RTL style properly - partial', () => {
const i18nStyle: ViewStyle = I18nLayoutService.toI18nStyle({
alignItems: 'flex-end',
justifyContent: 'flex-end',
flexDirection: 'row',
}, true);

expect(i18nStyle).toEqual({
alignItems: 'flex-start',
justifyContent: 'flex-start',
flexDirection: 'row-reverse',
});
});

});

0 comments on commit 14bf854

Please sign in to comment.