diff options
Diffstat (limited to 'subprojects/frontend/src/editor')
-rw-r--r-- | subprojects/frontend/src/editor/AnimatedButton.tsx | 95 | ||||
-rw-r--r-- | subprojects/frontend/src/editor/GenerateButton.tsx | 15 |
2 files changed, 103 insertions, 7 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 | ||