diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2021-12-30 10:03:26 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-01-24 16:54:33 +0100 |
| commit | 33b9e2ee55587638b52c6f319f20b1efe0b8522e (patch) | |
| tree | efb89ca2cb38edbb4f34f3b7be437440e4b548eb /gui/src | |
| parent | f418bb9692aa639ac77e6462a7cebffea71436c1 (diff) | |
| download | mullvadvpn-33b9e2ee55587638b52c6f319f20b1efe0b8522e.tar.xz mullvadvpn-33b9e2ee55587638b52c6f319f20b1efe0b8522e.zip | |
Position button label with CSS
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/renderer/components/AppButton.tsx | 97 | ||||
| -rw-r--r-- | gui/src/renderer/components/AppButtonStyles.tsx | 40 | ||||
| -rw-r--r-- | gui/src/renderer/components/TunnelControl.tsx | 4 |
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> ); }; |
