feishin/src/renderer/components/button/index.tsx

181 lines
5.4 KiB
TypeScript
Raw Normal View History

2022-12-19 15:59:14 -08:00
import type { ButtonProps as MantineButtonProps, TooltipProps } from '@mantine/core';
import type { Ref } from 'react';
import { createPolymorphicComponent, Button as MantineButton } from '@mantine/core';
2022-12-19 15:59:14 -08:00
import { useTimeout } from '@mantine/hooks';
import React, { forwardRef, useCallback, useRef, useState } from 'react';
2022-12-19 15:59:14 -08:00
import styled from 'styled-components';
2022-12-19 15:59:14 -08:00
import { Spinner } from '/@/renderer/components/spinner';
import { Tooltip } from '/@/renderer/components/tooltip';
export interface ButtonProps extends MantineButtonProps {
2023-07-01 19:10:05 -07:00
children: React.ReactNode;
loading?: boolean;
onClick?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
onMouseDown?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
tooltip?: Omit<TooltipProps, 'children'>;
2022-12-19 15:59:14 -08:00
}
interface StyledButtonProps extends ButtonProps {
2023-07-01 19:10:05 -07:00
ref: Ref<HTMLButtonElement>;
2022-12-19 15:59:14 -08:00
}
const StyledButton = styled(MantineButton)<StyledButtonProps>`
2023-03-09 18:08:15 -08:00
color: ${(props) => `var(--btn-${props.variant}-fg)`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
2023-07-01 19:10:05 -07:00
border: ${(props) => `var(--btn-${props.variant}-border)`};
border-radius: ${(props) => `var(--btn-${props.variant}-radius)`};
2024-08-23 08:19:27 -07:00
transition:
background 0.2s ease-in-out,
color 0.2s ease-in-out,
border 0.2s ease-in-out;
2022-12-19 15:59:14 -08:00
2023-07-01 19:10:05 -07:00
svg {
fill: ${(props) => `var(--btn-${props.variant}-fg)`};
2023-09-15 20:42:38 -07:00
transition: fill 0.2s ease-in-out;
2023-07-01 19:10:05 -07:00
}
2022-12-19 15:59:14 -08:00
2023-07-01 19:10:05 -07:00
&:disabled {
color: ${(props) => `var(--btn-${props.variant}-fg)`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
2022-12-19 15:59:14 -08:00
2023-07-01 19:10:05 -07:00
opacity: 0.6;
}
&:not([data-disabled])&:hover {
color: ${(props) => `var(--btn-${props.variant}-fg) !important`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
filter: brightness(85%);
2023-07-01 19:10:05 -07:00
border: ${(props) => `var(--btn-${props.variant}-border-hover)`};
svg {
fill: ${(props) => `var(--btn-${props.variant}-fg-hover)`};
}
}
&:not([data-disabled])&:focus-visible {
color: ${(props) => `var(--btn-${props.variant}-fg-hover)`};
background: ${(props) => `var(--btn-${props.variant}-bg)`};
filter: brightness(85%);
2023-07-01 19:10:05 -07:00
}
& .mantine-Button-centerLoader {
display: none;
}
& .mantine-Button-leftIcon {
display: flex;
height: 100%;
margin-right: 0.5rem;
}
.mantine-Button-rightIcon {
display: flex;
margin-left: 0.5rem;
2022-12-19 15:59:14 -08:00
}
`;
const ButtonChildWrapper = styled.span<{ $loading?: boolean }>`
2023-07-01 19:10:05 -07:00
color: ${(props) => props.$loading && 'transparent !important'};
2022-12-19 15:59:14 -08:00
`;
const SpinnerWrapper = styled.div`
2023-07-01 19:10:05 -07:00
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
2022-12-19 15:59:14 -08:00
`;
export const _Button = forwardRef<HTMLButtonElement, ButtonProps>(
2023-07-01 19:10:05 -07:00
({ children, tooltip, ...props }: ButtonProps, ref) => {
if (tooltip) {
return (
<Tooltip
withinPortal
{...tooltip}
>
<StyledButton
loaderPosition="center"
ref={ref}
2023-07-01 19:10:05 -07:00
{...props}
>
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
{props.loading && (
<SpinnerWrapper>
<Spinner />
</SpinnerWrapper>
)}
</StyledButton>
</Tooltip>
);
}
return (
<StyledButton
loaderPosition="center"
ref={ref}
2023-07-01 19:10:05 -07:00
{...props}
>
<ButtonChildWrapper $loading={props.loading}>{children}</ButtonChildWrapper>
{props.loading && (
<SpinnerWrapper>
<Spinner />
</SpinnerWrapper>
)}
</StyledButton>
);
},
2022-12-19 15:59:14 -08:00
);
export const Button = createPolymorphicComponent<'button', ButtonProps>(_Button);
interface HoldButtonProps extends ButtonProps {
2023-07-01 19:10:05 -07:00
timeoutProps: {
callback: () => void;
duration: number;
};
2022-12-19 15:59:14 -08:00
}
export const TimeoutButton = ({ timeoutProps, ...props }: HoldButtonProps) => {
2023-07-01 19:10:05 -07:00
const [, setTimeoutRemaining] = useState(timeoutProps.duration);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(0);
const callback = () => {
timeoutProps.callback();
setTimeoutRemaining(timeoutProps.duration);
clearInterval(intervalRef.current);
setIsRunning(false);
};
const { clear, start } = useTimeout(callback, timeoutProps.duration);
2023-07-01 19:10:05 -07:00
const startTimeout = useCallback(() => {
if (isRunning) {
clearInterval(intervalRef.current);
setIsRunning(false);
clear();
} else {
setIsRunning(true);
start();
const intervalId = window.setInterval(() => {
setTimeoutRemaining((prev) => prev - 100);
}, 100);
intervalRef.current = intervalId;
}
}, [clear, isRunning, start]);
return (
<Button
onClick={startTimeout}
sx={{ color: 'var(--danger-color)' }}
2023-07-01 19:10:05 -07:00
{...props}
>
{isRunning ? 'Cancel' : props.children}
</Button>
);
2022-12-19 15:59:14 -08:00
};