1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
/*
* SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
*
* SPDX-License-Identifier: EPL-2.0
*/
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { styled, type SxProps, type Theme } from '@mui/material/styles';
import { type ReactNode, useLayoutEffect, useState } from 'react';
const AnimatedButtonBase = styled(Button, {
shouldForwardProp: (prop) => prop !== 'width',
})<{ width: string }>(({ theme, width }) => {
// Transition copied from `@mui/material/Button`.
const colorTransition = theme.transitions.create(
['background-color', 'box-shadow', 'border-color', 'color'],
{
duration: theme.transitions.duration.short,
},
);
return {
width,
// Make sure the button does not change width if a number is updated.
fontVariantNumeric: 'tabular-nums',
transition: `
${colorTransition},
${theme.transitions.create(['width'], {
duration: theme.transitions.duration.short,
})}
`,
'@media (prefers-reduced-motion: reduce)': {
transition: colorTransition,
},
};
});
export default function AnimatedButton({
'aria-label': ariaLabel,
onClick,
color,
disabled,
startIcon,
sx,
children,
}: {
'aria-label'?: string;
onClick?: () => void;
color: 'error' | 'warning' | 'primary' | 'inherit';
disabled?: boolean;
startIcon: JSX.Element;
sx?: SxProps<Theme> | undefined;
children?: ReactNode;
}): JSX.Element {
const [width, setWidth] = useState<string | undefined>();
const [contentsElement, setContentsElement] = useState<HTMLDivElement | null>(
null,
);
useLayoutEffect(() => {
if (contentsElement !== null) {
const updateWidth = () => {
setWidth(window.getComputedStyle(contentsElement).width);
};
updateWidth();
const observer = new ResizeObserver(updateWidth);
observer.observe(contentsElement);
return () => observer.unobserve(contentsElement);
}
return () => {};
}, [setWidth, contentsElement]);
return (
<AnimatedButtonBase
{...(ariaLabel === undefined ? {} : { 'aria-label': ariaLabel })}
{...(onClick === undefined ? {} : { onClick })}
{...(sx === undefined ? {} : { sx })}
color={color}
className="rounded shaded"
disabled={disabled ?? false}
startIcon={startIcon}
width={width === undefined ? 'auto' : `calc(${width} + 50px)`}
>
<Box
display="flex"
flexDirection="row"
justifyContent="end"
overflow="hidden"
width="100%"
>
<Box whiteSpace="nowrap" ref={setContentsElement}>
{children}
</Box>
</Box>
</AnimatedButtonBase>
);
}
AnimatedButton.defaultProps = {
'aria-label': undefined,
onClick: undefined,
disabled: false,
sx: undefined,
children: undefined,
};
|