summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-12-30 10:03:26 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-01-24 16:54:33 +0100
commit33b9e2ee55587638b52c6f319f20b1efe0b8522e (patch)
treeefb89ca2cb38edbb4f34f3b7be437440e4b548eb /gui/src
parentf418bb9692aa639ac77e6462a7cebffea71436c1 (diff)
downloadmullvadvpn-33b9e2ee55587638b52c6f319f20b1efe0b8522e.tar.xz
mullvadvpn-33b9e2ee55587638b52c6f319f20b1efe0b8522e.zip
Position button label with CSS
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/AppButton.tsx97
-rw-r--r--gui/src/renderer/components/AppButtonStyles.tsx40
-rw-r--r--gui/src/renderer/components/TunnelControl.tsx4
3 files changed, 72 insertions, 69 deletions
diff --git a/gui/src/renderer/components/AppButton.tsx b/gui/src/renderer/components/AppButton.tsx
index a761179924..b4329bed61 100644
--- a/gui/src/renderer/components/AppButton.tsx
+++ b/gui/src/renderer/components/AppButton.tsx
@@ -1,36 +1,26 @@
-import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
+import React, { useCallback, useContext, useMemo, useState } from 'react';
import styled from 'styled-components';
import { colors } from '../../config.json';
import log from '../../shared/logging';
import { useMounted } from '../lib/utilityHooks';
import {
StyledButtonContent,
+ StyledHiddenSide,
StyledLabel,
- StyledLabelContainer,
+ StyledLeft,
+ StyledRight,
+ StyledVisibleSide,
transparentButton,
} from './AppButtonStyles';
import ImageView from './ImageView';
-interface IButtonContext {
- textAdjustment: number;
- textRef?: React.Ref<HTMLDivElement>;
-}
-
-const ButtonContext = React.createContext<IButtonContext>({
- textAdjustment: 0,
-});
-
interface ILabelProps {
- children?: React.ReactText;
+ textOffset?: number;
+ children?: React.ReactNode;
}
export function Label(props: ILabelProps) {
- const { textAdjustment, textRef } = useContext(ButtonContext);
- return (
- <StyledLabelContainer ref={textRef} textAdjustment={textAdjustment}>
- <StyledLabel>{props.children}</StyledLabel>
- </StyledLabelContainer>
- );
+ return <StyledLabel textOffset={props.textOffset ?? 0}>{props.children}</StyledLabel>;
}
interface IIconProps {
@@ -51,55 +41,52 @@ export interface IProps extends React.HTMLAttributes<HTMLButtonElement> {
textOffset?: number;
}
+type ChildrenGroups = { left: React.ReactNode[]; label: React.ReactNode; right: React.ReactNode[] };
+
const BaseButton = React.memo(function BaseButtonT(props: IProps) {
const { children, textOffset, ...otherProps } = props;
- const [textAdjustment, setTextAdjustment] = useState(0);
- const buttonRef = useRef() as React.RefObject<HTMLButtonElement>;
- const textRef = useRef() as React.RefObject<HTMLDivElement>;
-
- const contextValue = useMemo(() => ({ textAdjustment, textRef }), [textAdjustment, textRef]);
-
- useEffect(() => {
- const buttonRect = buttonRef.current?.getBoundingClientRect();
- const textRect = textRef.current?.getBoundingClientRect();
-
- if (buttonRect && textRect) {
- const leftDiff = textRect.left - buttonRect.left;
+ const groupedChildren = useMemo(() => {
+ return React.Children.toArray(children).reduce(
+ (groups: ChildrenGroups, child) => {
+ if (groups.label === undefined && typeof child === 'string') {
+ return { ...groups, label: <Label textOffset={textOffset}>{child}</Label> };
+ } else if (React.isValidElement(child) && child.type === Label) {
+ return { ...groups, label: React.cloneElement(child, { textOffset }) };
+ } else if (groups.label === undefined) {
+ return { ...groups, left: [...groups.left, child] };
+ } else {
+ return { ...groups, right: [...groups.right, child] };
+ }
+ },
+ { left: [], label: undefined, right: [] },
+ );
+ }, [children, textOffset]);
- // calculate the remaining space at the right hand side
- const trailingSpace = buttonRect.width - (leftDiff + textRect.width);
-
- // calculate text adjustment
- const textAdjustment = leftDiff - trailingSpace - (textOffset ?? 0);
+ return (
+ <StyledSimpleButton {...otherProps}>
+ <StyledButtonContent>
+ <StyledLeft>
+ <StyledVisibleSide>{groupedChildren.left}</StyledVisibleSide>
+ <StyledHiddenSide>{groupedChildren.right}</StyledHiddenSide>
+ </StyledLeft>
- // re-render the view with the new text adjustment if it changed
- setTextAdjustment(textAdjustment);
- }
- });
+ {groupedChildren.label ?? <Label />}
- return (
- <ButtonContext.Provider value={contextValue}>
- <StyledSimpleButton ref={buttonRef} {...otherProps}>
- <StyledButtonContent>
- {React.Children.map(children, (child) =>
- typeof child === 'string' ? <Label>{child as string}</Label> : child,
- )}
- </StyledButtonContent>
- </StyledSimpleButton>
- </ButtonContext.Provider>
+ <StyledRight>
+ <StyledVisibleSide>{groupedChildren.right}</StyledVisibleSide>
+ <StyledHiddenSide>{groupedChildren.left}</StyledHiddenSide>
+ </StyledRight>
+ </StyledButtonContent>
+ </StyledSimpleButton>
);
});
-function SimpleButtonT(
- props: React.ButtonHTMLAttributes<HTMLButtonElement>,
- ref: React.Ref<HTMLButtonElement>,
-) {
+function SimpleButtonT(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {
const blockingContext = useContext(BlockingContext);
return (
<button
- ref={ref}
{...props}
disabled={props.disabled || blockingContext.disabled}
onClick={blockingContext.onClick ?? props.onClick}>
@@ -108,7 +95,7 @@ function SimpleButtonT(
);
}
-export const SimpleButton = React.memo(React.forwardRef(SimpleButtonT));
+export const SimpleButton = React.memo(SimpleButtonT);
const StyledSimpleButton = styled(SimpleButton)({
display: 'flex',
diff --git a/gui/src/renderer/components/AppButtonStyles.tsx b/gui/src/renderer/components/AppButtonStyles.tsx
index 5446e89b31..6322a33783 100644
--- a/gui/src/renderer/components/AppButtonStyles.tsx
+++ b/gui/src/renderer/components/AppButtonStyles.tsx
@@ -1,27 +1,41 @@
import styled from 'styled-components';
import { buttonText } from './common-styles';
-export const StyledLabelContainer = styled.div((props: { textAdjustment: number }) => ({
- display: 'flex',
- flex: 1,
- paddingRight: `${props.textAdjustment > 0 ? props.textAdjustment : 0}px`,
- paddingLeft: `${props.textAdjustment < 0 ? Math.abs(props.textAdjustment) : 0}px`,
-}));
-
-export const StyledLabel = styled.span(buttonText, {
- flex: 1,
+export const StyledLabel = styled.span(buttonText, (props: { textOffset: number }) => ({
+ paddingLeft: props.textOffset > 0 ? `${props.textOffset}px` : 0,
+ paddingRight: props.textOffset < 0 ? `${-props.textOffset}px` : 0,
textAlign: 'center',
-});
+ wordBreak: 'break-word',
+}));
export const StyledButtonContent = styled.div({
- display: 'flex',
flex: 1,
- flexDirection: 'row',
+ display: 'grid',
+ gridTemplateColumns: '1fr auto 1fr',
alignItems: 'center',
- justifyContent: 'center',
padding: '9px',
});
export const transparentButton = {
backdropFilter: 'blur(4px)',
};
+
+export const StyledLeft = styled.div({
+ justifySelf: 'start',
+ display: 'flex',
+ flexDirection: 'column',
+});
+
+export const StyledRight = styled(StyledLeft)({
+ justifySelf: 'end',
+});
+
+export const StyledVisibleSide = styled.div({
+ display: 'flex',
+ flexDirection: 'row',
+});
+
+export const StyledHiddenSide = styled(StyledVisibleSide).attrs({ 'aria-hidden': true })({
+ height: 0,
+ visibility: 'hidden',
+});
diff --git a/gui/src/renderer/components/TunnelControl.tsx b/gui/src/renderer/components/TunnelControl.tsx
index c0fa5f8452..441e95bbb1 100644
--- a/gui/src/renderer/components/TunnelControl.tsx
+++ b/gui/src/renderer/components/TunnelControl.tsx
@@ -268,7 +268,9 @@ export default class TunnelControl extends React.Component<ITunnelControlProps>
onClick={this.props.onReconnect}
aria-label={messages.gettext('Reconnect')}
{...props}>
- <ImageView height={22} width={22} source="icon-reload" tintColor="white" />
+ <AppButton.Label>
+ <ImageView height={22} width={22} source="icon-reload" tintColor="white" />
+ </AppButton.Label>
</AppButton.RedTransparentButton>
);
};