diff options
| author | Oliver <oliver@mohlin.dev> | 2025-02-27 10:14:50 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2025-03-05 13:16:36 +0100 |
| commit | 2bfe437690baf5baf358567d9a3249a7f30a6f5a (patch) | |
| tree | 92f3c2503d93e0e42edc136412a4d99fb8a6772f | |
| parent | ec359a9b014e2e9c82bc3cff32378647bd92f8d6 (diff) | |
| download | mullvadvpn-2bfe437690baf5baf358567d9a3249a7f30a6f5a.tar.xz mullvadvpn-2bfe437690baf5baf358567d9a3249a7f30a6f5a.zip | |
Make button composable
13 files changed, 145 insertions, 64 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx index 4c4b991829..0fe1150139 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/InfoButton.tsx @@ -27,7 +27,7 @@ export default function InfoButton({ title, message, children, ...props }: InfoB type={ModalAlertType.info} buttons={[ <Button key="back" onClick={hide}> - {messages.gettext('Got it!')} + <Button.Text>{messages.gettext('Got it!')}</Button.Text> </Button>, ]} close={hide}> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx index bbd4a32584..34a04d1dc0 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Login.tsx @@ -374,11 +374,8 @@ class Login extends React.Component<IProps, IState> { <LabelTiny color={Colors.white60}> {messages.pgettext('login-view', 'Don’t have an account number?')} </LabelTiny> - <Button - size="full" - onClick={this.props.createNewAccount} - disabled={!this.allowCreateAccount()}> - {messages.pgettext('login-view', 'Create account')} + <Button onClick={this.props.createNewAccount} disabled={!this.allowCreateAccount()}> + <Button.Text>{messages.pgettext('login-view', 'Create account')}</Button.Text> </Button> </Flex> ); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx index 5a50224d80..53dded0411 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/Settings.tsx @@ -238,9 +238,11 @@ function QuitButton() { return ( <Button variant="destructive" onClick={quit}> - {tunnelState.state === 'disconnected' - ? messages.gettext('Quit') - : messages.gettext('Disconnect & quit')} + <Button.Text> + {tunnelState.state === 'disconnected' + ? messages.gettext('Quit') + : messages.gettext('Disconnect & quit')} + </Button.Text> </Button> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx index c80d7a1276..fe543317ca 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/SplitTunnelingSettings.tsx @@ -537,7 +537,9 @@ export function SplitTunnelingSettings(props: IPlatformSplitTunnelingSettingsPro {canEditSplitTunneling && ( <Container size="3"> <Button onClick={addWithFilePicker}> - {messages.pgettext('split-tunneling-view', 'Find another app')} + <Button.Text> + {messages.pgettext('split-tunneling-view', 'Find another app')} + </Button.Text> </Button> </Container> )} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx index ce1f4f3fd1..f843f4a08a 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/TooManyDevices.tsx @@ -136,12 +136,16 @@ export default function TooManyDevices() { variant="success" onClick={continueLogin} disabled={continueButtonDisabled}> - { - // TRANSLATORS: Button for continuing login process. - messages.pgettext('device-management', 'Continue with login') - } + <Button.Text> + { + // TRANSLATORS: Button for continuing login process. + messages.pgettext('device-management', 'Continue with login') + } + </Button.Text> + </Button> + <Button onClick={cancel}> + <Button.Text>{messages.gettext('Back')}</Button.Text> </Button> - <Button onClick={cancel}>{messages.gettext('Back')}</Button> </AppButton.ButtonGroup> </Footer> )} diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx index c403ddb849..c7b6c61d29 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/ConnectionActionButton.tsx @@ -30,7 +30,7 @@ function ConnectButton(props: Partial<Parameters<typeof Button>[0]>) { return ( <Button variant="success" onClick={onConnect} {...props}> - {messages.pgettext('tunnel-control', 'Connect')} + <Button.Text>{messages.pgettext('tunnel-control', 'Connect')}</Button.Text> </Button> ); } @@ -52,7 +52,9 @@ function DisconnectButton() { return ( <Button variant="destructive" onClick={onDisconnect}> - {displayAsCancel ? messages.gettext('Cancel') : messages.gettext('Disconnect')} + <Button.Text> + {displayAsCancel ? messages.gettext('Cancel') : messages.gettext('Disconnect')} + </Button.Text> </Button> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx index c484c0312d..df1c9246e3 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/main-view/SelectLocationButton.tsx @@ -42,17 +42,17 @@ function SelectLocationButton(props: ButtonProps) { return ( <Button - variant="primary" - size="full" onClick={onSelectLocation} aria-label={sprintf( messages.pgettext('accessibility', 'Select location. Current location is %(location)s'), { location: selectedRelayName }, )} {...props}> - {tunnelState === 'disconnected' - ? selectedRelayName - : messages.pgettext('tunnel-control', 'Switch location')} + <Button.Text> + {tunnelState === 'disconnected' + ? selectedRelayName + : messages.pgettext('tunnel-control', 'Switch location')} + </Button.Text> </Button> ); } diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/Button.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/Button.tsx index 5c8f9d05b3..81577ad975 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/Button.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/Button.tsx @@ -4,13 +4,12 @@ import styled from 'styled-components'; import { Colors, Radius, Spacings } from '../../foundations'; import { buttonReset } from '../../styles'; import { Flex } from '../flex'; -import { BodySmallSemiBold } from '../typography'; +import { ButtonProvider } from './ButtonContext'; +import { ButtonIcon, ButtonText, StyledIcon, StyledText } from './components'; export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { variant?: 'primary' | 'success' | 'destructive'; size?: 'auto' | 'full' | '1/2'; - leading?: React.ReactNode; - trailing?: React.ReactNode; } const variants = { @@ -57,49 +56,73 @@ const StyledButton = styled.button({ }, }); -export const Button = forwardRef<HTMLButtonElement, ButtonProps>( - ( - { variant = 'primary', size = 'full', leading, trailing, children, disabled, style, ...props }, - ref, - ) => { +const StyledFlex = styled(Flex)` + justify-content: space-between; + &&:has(${StyledText}:only-child) { + justify-content: center; + } + &&:has(${StyledText} + ${StyledIcon}) { + &::before { + content: ' '; + display: inline-block; + width: 24px; + } + } + &&:has(${StyledIcon} + ${StyledText}) { + &::after { + content: ' '; + display: inline-block; + width: 24px; + } + } + &&:has(${StyledIcon} + ${StyledText} + ${StyledIcon}) { + &::before { + display: none; + } + &::after { + display: none; + } + } +`; + +const Button = forwardRef<HTMLButtonElement, ButtonProps>( + ({ variant = 'primary', size = 'full', children, disabled = false, style, ...props }, ref) => { const styles = variants[variant]; return ( - <StyledButton - ref={ref} - style={ - { - '--background': styles.background, - '--hover': styles.hover, - '--disabled': styles.disabled, - '--size': sizes[size], - ...style, - } as React.CSSProperties - } - disabled={disabled} - {...props}> - <Flex - $flex={1} - $gap={Spacings.spacing3} - $justifyContent="space-between" - $padding={{ - horizontal: Spacings.spacing3, - }} - $alignItems="center"> - {leading} - <Flex $flex={1} $justifyContent="center" $alignItems="center"> - {typeof children === 'string' ? ( - <BodySmallSemiBold color={disabled ? Colors.white40 : Colors.white}> - {children} - </BodySmallSemiBold> - ) : ( - children - )} - </Flex> - {trailing} - </Flex> - </StyledButton> + <ButtonProvider disabled={disabled}> + <StyledButton + ref={ref} + style={ + { + '--background': styles.background, + '--hover': styles.hover, + '--disabled': styles.disabled, + '--size': sizes[size], + ...style, + } as React.CSSProperties + } + disabled={disabled} + {...props}> + <StyledFlex + $flex={1} + $gap={Spacings.spacing3} + $alignItems="center" + $padding={{ + horizontal: Spacings.spacing3, + }}> + {children} + </StyledFlex> + </StyledButton> + </ButtonProvider> ); }, ); Button.displayName = 'Button'; + +const ButtonNamespace = Object.assign(Button, { + Text: ButtonText, + Icon: ButtonIcon, +}); + +export { ButtonNamespace as Button }; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/ButtonContext.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/ButtonContext.tsx new file mode 100644 index 0000000000..4ff4876723 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/ButtonContext.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +interface ButtonContextProps { + disabled: boolean; +} + +const ButtonContext = React.createContext<ButtonContextProps | undefined>(undefined); + +export const useButtonContext = (): ButtonContextProps => { + const context = React.useContext(ButtonContext); + if (!context) { + throw new Error('useButtonContext must be used within a ButtonProvider'); + } + return context; +}; + +interface ButtonProviderProps { + disabled: boolean; + children: React.ReactNode; +} + +export const ButtonProvider = ({ disabled, children }: ButtonProviderProps) => { + return <ButtonContext.Provider value={{ disabled }}>{children}</ButtonContext.Provider>; +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/ButtonIcon.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/ButtonIcon.tsx new file mode 100644 index 0000000000..fcf0959a82 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/ButtonIcon.tsx @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +import { Icon, IconProps } from '../../icon'; + +type ButtonIconProps = Omit<IconProps, 'size'>; + +export const StyledIcon = styled(Icon)({}); + +export const ButtonIcon = ({ ...props }: ButtonIconProps) => { + return <StyledIcon size="medium" {...props} />; +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/ButtonText.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/ButtonText.tsx new file mode 100644 index 0000000000..de117fccb2 --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/ButtonText.tsx @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +import { Colors } from '../../../foundations'; +import { BodySmallSemiBold, BodySmallSemiBoldProps } from '../../typography'; +import { useButtonContext } from '../ButtonContext'; + +export type ButtonTextProps = Omit<BodySmallSemiBoldProps, 'color'>; +export const StyledText = styled(BodySmallSemiBold)``; + +export const ButtonText = (props: ButtonTextProps) => { + const { disabled } = useButtonContext(); + return <StyledText color={disabled ? Colors.white40 : Colors.white} {...props} />; +}; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/index.ts new file mode 100644 index 0000000000..904258378e --- /dev/null +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/components/index.ts @@ -0,0 +1,2 @@ +export * from './ButtonIcon'; +export * from './ButtonText'; diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/index.ts index 8b166a86e4..886fa8fe20 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/index.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/button/index.ts @@ -1 +1,2 @@ export * from './Button'; +export * from './ButtonContext'; |
