summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2019-03-05 13:16:09 +0100
committerAndrej Mihajlov <and@mullvad.net>2019-03-07 13:48:30 +0100
commit35a531578061085c4724eb54f03c4f7204ebfe5e (patch)
tree0626e0ec6dbd952b7880106f10ca40e21c910872 /gui/src
parentafca6149d3d385ec1b95dcd77d9e75a08d874c31 (diff)
downloadmullvadvpn-35a531578061085c4724eb54f03c4f7204ebfe5e.tar.xz
mullvadvpn-35a531578061085c4724eb54f03c4f7204ebfe5e.zip
Automatically center the text label within the AppButton
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/AppButton.tsx137
-rw-r--r--gui/src/renderer/components/AppButtonStyles.tsx23
2 files changed, 123 insertions, 37 deletions
diff --git a/gui/src/renderer/components/AppButton.tsx b/gui/src/renderer/components/AppButton.tsx
index 17aafb7fc1..34a7f46e04 100644
--- a/gui/src/renderer/components/AppButton.tsx
+++ b/gui/src/renderer/components/AppButton.tsx
@@ -1,16 +1,53 @@
import * as React from 'react';
-import { Button, Component, Text, Types } from 'reactxp';
+import { Button, Component, Styles, Text, Types, UserInterface, View } from 'reactxp';
import { colors } from '../../config.json';
import styles from './AppButtonStyles';
import ImageView from './ImageView';
+const ButtonContext = React.createContext({
+ textAdjustment: 0,
+ textRef: React.createRef<PrivateLabel>(),
+});
+
interface ILabelProps {
children?: React.ReactText;
}
+interface IPrivateLabelProps {
+ textAdjustment: number;
+ children?: React.ReactText;
+}
+
+class PrivateLabel extends Component<IPrivateLabelProps> {
+ public render() {
+ const { textAdjustment, children } = this.props;
+ const textAdjustmentStyle = Styles.createViewStyle(
+ {
+ paddingRight: textAdjustment > 0 ? textAdjustment : 0,
+ paddingLeft: textAdjustment < 0 ? Math.abs(textAdjustment) : 0,
+ },
+ false,
+ );
+
+ return (
+ <View style={[styles.labelContainer, textAdjustmentStyle]}>
+ <Text style={styles.label}>{children}</Text>
+ </View>
+ );
+ }
+}
+
export class Label extends Component<ILabelProps> {
public render() {
- return <Text style={styles.label}>{this.props.children}</Text>;
+ return (
+ <ButtonContext.Consumer>
+ {(context) => (
+ <PrivateLabel ref={context.textRef} textAdjustment={context.textAdjustment}>
+ {this.props.children}
+ </PrivateLabel>
+ )}
+ </ButtonContext.Consumer>
+ );
}
}
@@ -28,7 +65,6 @@ export class Icon extends Component<IIconProps> {
width={this.props.width}
height={this.props.height}
tintColor={colors.white}
- style={styles.icon}
/>
);
}
@@ -43,52 +79,107 @@ interface IProps {
interface IState {
hovered: boolean;
+ textAdjustment: number;
}
class BaseButton extends Component<IProps, IState> {
- public state: IState = { hovered: false };
-
- public backgroundStyle = (): Types.ButtonStyleRuleSet => {
- throw new Error('Implement backgroundStyle in subclasses.');
+ public state: IState = {
+ hovered: false,
+ textAdjustment: 0,
};
- public onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null);
- public onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null);
+
+ private containerRef = React.createRef<View>();
+ private textViewRef = React.createRef<PrivateLabel>();
+
+ public componentDidMount() {
+ this.forceUpdateTextAdjustment();
+ }
public render() {
const { children, style, ...otherProps } = this.props;
return (
- <Button
- {...otherProps}
- style={[styles.common, this.backgroundStyle(), style]}
- onHoverStart={this.onHoverStart}
- onHoverEnd={this.onHoverEnd}>
- {React.Children.map(children, (child) =>
- typeof child === 'string' ? <Label>{child as string}</Label> : child,
- )}
- </Button>
+ <ButtonContext.Provider
+ value={{
+ textAdjustment: this.state.textAdjustment,
+ textRef: this.textViewRef,
+ }}>
+ <Button
+ {...otherProps}
+ style={[styles.common, this.backgroundStyle(), style]}
+ onHoverStart={this.onHoverStart}
+ onHoverEnd={this.onHoverEnd}>
+ <View style={styles.content} ref={this.containerRef} onLayout={this.onLayout}>
+ {React.Children.map(children, (child) =>
+ typeof child === 'string' ? <Label>{child as string}</Label> : child,
+ )}
+ </View>
+ </Button>
+ </ButtonContext.Provider>
);
}
+
+ protected backgroundStyle = (): Types.ButtonStyleRuleSet => {
+ throw new Error('Implement backgroundStyle in subclasses.');
+ };
+ protected onHoverStart = () => (!this.props.disabled ? this.setState({ hovered: true }) : null);
+ protected onHoverEnd = () => (!this.props.disabled ? this.setState({ hovered: false }) : null);
+
+ private async forceUpdateTextAdjustment() {
+ const containerView = this.containerRef.current;
+ if (containerView) {
+ const containerLayout = await UserInterface.measureLayoutRelativeToAncestor(
+ containerView,
+ this,
+ );
+
+ this.updateTextAdjustment(containerLayout);
+ }
+ }
+
+ private async updateTextAdjustment(containerLayout: Types.LayoutInfo) {
+ const labelView = this.textViewRef.current;
+
+ if (labelView) {
+ // calculate the title layout frame
+ const labelLayout = await UserInterface.measureLayoutRelativeToAncestor(labelView, this);
+
+ // calculate the remaining space at the right hand side
+ const trailingSpace = containerLayout.width - (labelLayout.x + labelLayout.width);
+
+ // calculate text adjustment
+ const textAdjustment = labelLayout.x - trailingSpace;
+
+ // re-render the view with the new text adjustment if it changed
+ if (this.state.textAdjustment !== textAdjustment) {
+ this.setState({ textAdjustment });
+ }
+ }
+ }
+
+ private onLayout = async (containerLayout: Types.ViewOnLayoutEvent) => {
+ this.updateTextAdjustment(containerLayout);
+ };
}
export class RedButton extends BaseButton {
- public backgroundStyle = () => (this.state.hovered ? styles.redHover : styles.red);
+ protected backgroundStyle = () => (this.state.hovered ? styles.redHover : styles.red);
}
export class GreenButton extends BaseButton {
- public backgroundStyle = () => (this.state.hovered ? styles.greenHover : styles.green);
+ protected backgroundStyle = () => (this.state.hovered ? styles.greenHover : styles.green);
}
export class BlueButton extends BaseButton {
- public backgroundStyle = () => (this.state.hovered ? styles.blueHover : styles.blue);
+ protected backgroundStyle = () => (this.state.hovered ? styles.blueHover : styles.blue);
}
export class TransparentButton extends BaseButton {
- public backgroundStyle = () =>
+ protected backgroundStyle = () =>
this.state.hovered ? styles.transparentHover : styles.transparent;
}
export class RedTransparentButton extends BaseButton {
- public backgroundStyle = () =>
+ protected backgroundStyle = () =>
this.state.hovered ? styles.redTransparentHover : styles.redTransparent;
}
diff --git a/gui/src/renderer/components/AppButtonStyles.tsx b/gui/src/renderer/components/AppButtonStyles.tsx
index c5cc6dfc6b..055ce9b031 100644
--- a/gui/src/renderer/components/AppButtonStyles.tsx
+++ b/gui/src/renderer/components/AppButtonStyles.tsx
@@ -32,31 +32,26 @@ export default {
redTransparentHover: Styles.createButtonStyle({
backgroundColor: colors.red45,
}),
- icon: Styles.createViewStyle({
- position: 'absolute',
- alignSelf: 'flex-end',
- right: 8,
- marginLeft: 8,
- }),
common: Styles.createViewStyle({
cursor: 'default',
- paddingTop: 9,
- paddingLeft: 9,
- paddingRight: 9,
- paddingBottom: 9,
borderRadius: 4,
+ }),
+ content: Styles.createViewStyle({
+ flex: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ padding: 9,
+ }),
+ labelContainer: Styles.createViewStyle({
flex: 1,
- flexDirection: 'column',
- alignContent: 'center',
- justifyContent: 'center',
}),
label: Styles.createTextStyle({
- alignSelf: 'center',
fontFamily: 'DINPro',
fontSize: 20,
fontWeight: '900',
lineHeight: 26,
flex: 1,
color: colors.white,
+ textAlign: 'center',
}),
};