diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-10-01 13:45:56 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-10-03 20:06:53 +0200 |
commit | b7436a1934cdff6d558abc262776c3a2d21df8f7 (patch) | |
tree | 86368c4484613ae707ca1c50a73173be1c6ca638 /subprojects/frontend/src | |
parent | fix(frontend): editor selection visibility (diff) | |
download | refinery-b7436a1934cdff6d558abc262776c3a2d21df8f7.tar.gz refinery-b7436a1934cdff6d558abc262776c3a2d21df8f7.tar.zst refinery-b7436a1934cdff6d558abc262776c3a2d21df8f7.zip |
feat(frontend): animate GenerateButton
Diffstat (limited to 'subprojects/frontend/src')
-rw-r--r-- | subprojects/frontend/src/editor/AnimatedButton.tsx | 95 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/GenerateButton.tsx | 15 | ||||
-rw-r--r-- | subprojects/frontend/src/theme/ThemeProvider.tsx | 15 |
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 @@ | |||
1 | import Box from '@mui/material/Box'; | ||
2 | import Button from '@mui/material/Button'; | ||
3 | import { styled } from '@mui/material/styles'; | ||
4 | import React, { type ReactNode, useLayoutEffect, useState } from 'react'; | ||
5 | |||
6 | const 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 | |||
31 | export 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 | |||
90 | AnimatedButton.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 @@ | |||
1 | import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined'; | ||
1 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; | 2 | import PlayArrowIcon from '@mui/icons-material/PlayArrow'; |
2 | import Button from '@mui/material/Button'; | 3 | import Button from '@mui/material/Button'; |
3 | import { observer } from 'mobx-react-lite'; | 4 | import { observer } from 'mobx-react-lite'; |
4 | import React from 'react'; | 5 | import React from 'react'; |
5 | 6 | ||
7 | import AnimatedButton from './AnimatedButton'; | ||
6 | import type EditorStore from './EditorStore'; | 8 | import type EditorStore from './EditorStore'; |
7 | 9 | ||
8 | const GENERATE_LABEL = 'Generate'; | 10 | const 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… | 22 | Loading… |
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: { |