aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-10-01 13:45:56 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-10-03 20:06:53 +0200
commitb7436a1934cdff6d558abc262776c3a2d21df8f7 (patch)
tree86368c4484613ae707ca1c50a73173be1c6ca638
parentfix(frontend): editor selection visibility (diff)
downloadrefinery-b7436a1934cdff6d558abc262776c3a2d21df8f7.tar.gz
refinery-b7436a1934cdff6d558abc262776c3a2d21df8f7.tar.zst
refinery-b7436a1934cdff6d558abc262776c3a2d21df8f7.zip
feat(frontend): animate GenerateButton
-rw-r--r--subprojects/frontend/src/editor/AnimatedButton.tsx95
-rw-r--r--subprojects/frontend/src/editor/GenerateButton.tsx15
-rw-r--r--subprojects/frontend/src/theme/ThemeProvider.tsx15
3 files changed, 110 insertions, 15 deletions
diff --git a/subprojects/frontend/src/editor/AnimatedButton.tsx b/subprojects/frontend/src/editor/AnimatedButton.tsx
new file mode 100644
index 00000000..d08decbc
--- /dev/null
+++ b/subprojects/frontend/src/editor/AnimatedButton.tsx
@@ -0,0 +1,95 @@
1import Box from '@mui/material/Box';
2import Button from '@mui/material/Button';
3import { styled } from '@mui/material/styles';
4import React, { type ReactNode, useLayoutEffect, useState } from 'react';
5
6const AnimatedButtonBase = styled(Button, {
7 shouldForwardProp: (prop) => prop !== 'width',
8})<{ width: string }>(({ theme, width }) => {
9 // Transition copied from `@mui/material/Button`.
10 const colorTransition = theme.transitions.create(
11 ['background-color', 'box-shadow', 'border-color', 'color'],
12 { duration: theme.transitions.duration.short },
13 );
14 return {
15 width,
16 // Make sure the button does not change width if a number is updated.
17 fontVariantNumeric: 'tabular-nums',
18 transition: `
19 ${colorTransition},
20 ${theme.transitions.create(['width'], {
21 duration: theme.transitions.duration.short,
22 easing: theme.transitions.easing.easeOut,
23 })}
24 `,
25 '@media (prefers-reduced-motion: reduce)': {
26 transition: colorTransition,
27 },
28 };
29});
30
31export default function AnimatedButton({
32 'aria-label': ariaLabel,
33 onClick,
34 color,
35 disabled,
36 startIcon,
37 children,
38}: {
39 'aria-label'?: string;
40 onClick?: () => void;
41 color: 'error' | 'warning' | 'primary' | 'inherit';
42 disabled?: boolean;
43 startIcon: JSX.Element;
44 children?: ReactNode;
45}): JSX.Element {
46 const [width, setWidth] = useState<string | undefined>();
47 const [contentsElement, setContentsElement] = useState<HTMLDivElement | null>(
48 null,
49 );
50
51 useLayoutEffect(() => {
52 if (contentsElement !== null) {
53 const updateWidth = () => {
54 setWidth(window.getComputedStyle(contentsElement).width);
55 };
56 updateWidth();
57 const observer = new ResizeObserver(updateWidth);
58 observer.observe(contentsElement);
59 return () => observer.unobserve(contentsElement);
60 }
61 return () => {};
62 }, [setWidth, contentsElement]);
63
64 return (
65 <AnimatedButtonBase
66 {...(ariaLabel === undefined ? {} : { 'aria-label': ariaLabel })}
67 {...(onClick === undefined ? {} : { onClick })}
68 color={color}
69 variant="outlined"
70 className="rounded"
71 disabled={disabled ?? false}
72 startIcon={startIcon}
73 width={width === undefined ? 'auto' : `calc(${width} + 50px)`}
74 >
75 <Box
76 display="flex"
77 flexDirection="row"
78 justifyContent="end"
79 overflow="hidden"
80 width="100%"
81 >
82 <Box whiteSpace="nowrap" ref={setContentsElement}>
83 {children}
84 </Box>
85 </Box>
86 </AnimatedButtonBase>
87 );
88}
89
90AnimatedButton.defaultProps = {
91 'aria-label': undefined,
92 onClick: undefined,
93 disabled: false,
94 children: undefined,
95};
diff --git a/subprojects/frontend/src/editor/GenerateButton.tsx b/subprojects/frontend/src/editor/GenerateButton.tsx
index 485989b5..8b6ae660 100644
--- a/subprojects/frontend/src/editor/GenerateButton.tsx
+++ b/subprojects/frontend/src/editor/GenerateButton.tsx
@@ -1,8 +1,10 @@
1import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined';
1import PlayArrowIcon from '@mui/icons-material/PlayArrow'; 2import PlayArrowIcon from '@mui/icons-material/PlayArrow';
2import Button from '@mui/material/Button'; 3import Button from '@mui/material/Button';
3import { observer } from 'mobx-react-lite'; 4import { observer } from 'mobx-react-lite';
4import React from 'react'; 5import React from 'react';
5 6
7import AnimatedButton from './AnimatedButton';
6import type EditorStore from './EditorStore'; 8import type EditorStore from './EditorStore';
7 9
8const GENERATE_LABEL = 'Generate'; 10const GENERATE_LABEL = 'Generate';
@@ -16,7 +18,7 @@ const GenerateButton = observer(function GenerateButton({
16}): JSX.Element { 18}): JSX.Element {
17 if (editorStore === undefined) { 19 if (editorStore === undefined) {
18 return ( 20 return (
19 <Button color="inherit" className="rounded" disabled> 21 <Button color="inherit" variant="outlined" className="rounded" disabled>
20 Loading&hellip; 22 Loading&hellip;
21 </Button> 23 </Button>
22 ); 24 );
@@ -35,26 +37,25 @@ const GenerateButton = observer(function GenerateButton({
35 37
36 if (errorCount > 0) { 38 if (errorCount > 0) {
37 return ( 39 return (
38 <Button 40 <AnimatedButton
39 aria-label={`Select next diagnostic out of ${summary}`} 41 aria-label={`Select next diagnostic out of ${summary}`}
40 onClick={() => editorStore.nextDiagnostic()} 42 onClick={() => editorStore.nextDiagnostic()}
41 color="error" 43 color="error"
42 className="rounded" 44 startIcon={<DangerousOutlinedIcon />}
43 > 45 >
44 {summary} 46 {summary}
45 </Button> 47 </AnimatedButton>
46 ); 48 );
47 } 49 }
48 50
49 return ( 51 return (
50 <Button 52 <AnimatedButton
51 disabled={!editorStore.opened} 53 disabled={!editorStore.opened}
52 color={warningCount > 0 ? 'warning' : 'primary'} 54 color={warningCount > 0 ? 'warning' : 'primary'}
53 className="rounded"
54 startIcon={<PlayArrowIcon />} 55 startIcon={<PlayArrowIcon />}
55 > 56 >
56 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`} 57 {summary === '' ? GENERATE_LABEL : `${GENERATE_LABEL} (${summary})`}
57 </Button> 58 </AnimatedButton>
58 ); 59 );
59}); 60});
60 61
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx
index 09a72d54..d990fd5d 100644
--- a/subprojects/frontend/src/theme/ThemeProvider.tsx
+++ b/subprojects/frontend/src/theme/ThemeProvider.tsx
@@ -93,15 +93,14 @@ const components: Components = {
93 '.MuiButton-startIcon': { marginRight: 6 }, 93 '.MuiButton-startIcon': { marginRight: 6 },
94 '.MuiButton-endIcon': { marginLeft: 6 }, 94 '.MuiButton-endIcon': { marginLeft: 6 },
95 }, 95 },
96 sizeSmall: { fontSize: '0.75rem' },
97 sizeLarge: { fontSize: '1rem' },
96 text: { '&.rounded': { padding: '6px 14px' } }, 98 text: { '&.rounded': { padding: '6px 14px' } },
97 textSizeSmall: { 99 textSizeSmall: { '&.rounded': { padding: '4px 8px' } },
98 fontSize: '0.875rem', 100 textSizeLarge: { '&.rounded': { padding: '8px 20px' } },
99 '&.rounded': { padding: '4px 8px' }, 101 outlined: { '&.rounded': { padding: '5px 13px' } },
100 }, 102 outlinedSizeSmall: { '&.rounded': { padding: '3px 9px' } },
101 textSizeLarge: { 103 outlinedSizeLarge: { '&.rounded': { padding: '7px 19px' } },
102 fontSize: '1rem',
103 '&.rounded': { padding: '8px 20px' },
104 },
105 }, 104 },
106 }, 105 },
107 MuiToggleButton: { 106 MuiToggleButton: {