import React, { useCallback, useContext, useEffect, useMemo, useRef, 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, StyledLabel, StyledLabelContainer } from './AppButtonStyles'; import ImageView from './ImageView'; interface IButtonContext { textAdjustment: number; textRef?: React.Ref; } const ButtonContext = React.createContext({ textAdjustment: 0, }); interface ILabelProps { children?: React.ReactText; } export function Label(props: ILabelProps) { const { textAdjustment, textRef } = useContext(ButtonContext); return ( {props.children} ); } interface IIconProps { source: string; width?: number; height?: number; } export function Icon(props: IIconProps) { return ; } export interface IProps extends React.HTMLAttributes { children?: React.ReactNode; className?: string; disabled?: boolean; onClick?: () => void; textOffset?: number; } const BaseButton = React.memo(function BaseButtonT(props: IProps) { const { children, textOffset, ...otherProps } = props; const [textAdjustment, setTextAdjustment] = useState(0); const buttonRef = useRef() as React.RefObject; const textRef = useRef() as React.RefObject; 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; // 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); // re-render the view with the new text adjustment if it changed setTextAdjustment(textAdjustment); } }); return ( {React.Children.map(children, (child) => typeof child === 'string' ? : child, )} ); }); function SimpleButtonT( props: React.ButtonHTMLAttributes, ref: React.Ref, ) { const blockingContext = useContext(BlockingContext); return ( ); } export const SimpleButton = React.memo(React.forwardRef(SimpleButtonT)); const StyledSimpleButton = styled(SimpleButton)({ display: 'flex', cursor: 'default', borderRadius: 4, border: 'none', padding: 0, ':disabled': { opacity: 0.5, }, }); interface IBlockingContext { disabled?: boolean; onClick?: () => Promise; } const BlockingContext = React.createContext({}); interface IBlockingProps { children?: React.ReactNode; onClick: () => Promise; disabled?: boolean; } export function BlockingButton(props: IBlockingProps) { const isMounted = useMounted(); const [isBlocked, setIsBlocked] = useState(false); const onClick = useCallback(async () => { setIsBlocked(true); try { await props.onClick(); } catch (error) { log.error(`onClick() failed - ${error}`); } if (isMounted()) { setIsBlocked(false); } }, [props.onClick]); const contextValue = useMemo( () => ({ disabled: isBlocked || props.disabled, onClick, }), [isBlocked, props.disabled, onClick], ); return {props.children}; } export const RedButton = styled(BaseButton)({ backgroundColor: colors.red, ':not(:disabled):hover': { backgroundColor: colors.red95, }, }); export const GreenButton = styled(BaseButton)({ backgroundColor: colors.green, ':not(:disabled):hover': { backgroundColor: colors.green90, }, }); export const BlueButton = styled(BaseButton)({ backgroundColor: colors.blue80, ':not(:disabled):hover': { backgroundColor: colors.blue60, }, }); export const TransparentButton = styled(BaseButton)({ backgroundColor: colors.white20, ':not(:disabled):hover': { backgroundColor: colors.white40, }, }); export const RedTransparentButton = styled(BaseButton)({ backgroundColor: colors.red60, ':not(:disabled):hover': { backgroundColor: colors.red80, }, });