Repository navigation
Expand file tree
/
Copy pathPanelCommand.tsx
More file actions
128 lines (119 loc) · 3.84 KB
/
Copy pathPanelCommand.tsx
File metadata and controls
128 lines (119 loc) · 3.84 KB
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import React, { FC, useCallback, useRef, useState } from 'react'
import { useSelector } from 'react-redux'
import { css, cx } from '../../../styled-system/css'
import { panelCommandRecipe } from '../../../styled-system/recipes'
import Command from '../../@types/Command'
import Icon from '../../@types/IconType'
import { isTouch } from '../../browser'
import { executeCommandWithMulticursor } from '../../commands'
import store from '../../stores/app'
import fastClick from '../../util/fastClick'
import FadeTransition from '../FadeTransition'
interface PanelCommandProps {
/** The command to execute when the button is tapped. */
command: Command
/** The size of the button. */
size?: 'small' | 'medium'
}
interface ActiveButtonGlowImageProps {
isActive: boolean | undefined
}
/** Pre-blurred additive glow that avoids Android corruption from filtered HSL blend layers. */
const ActiveButtonGlowImage: FC<ActiveButtonGlowImageProps> = ({ isActive }) => {
const nodeRef = useRef<HTMLDivElement>(null)
return (
<FadeTransition
type='activeButtonGlow'
in={isActive}
unmountOnExit
nodeRef={nodeRef}
/**
* When `in={true}` on mount, we need appear={true} to trigger the enter transition.
* We need the enter transition, because the opacity is set inside the transition styles.
**/
appear
>
<div
ref={nodeRef}
className={css({
gridArea: 'command',
margin: -40,
pointerEvents: 'none',
backgroundImage: 'url(/img/command-center/active-glow.png)',
backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat',
mixBlendMode: 'plus-lighter',
})}
/>
</FadeTransition>
)
}
/** A single button in the Panel Command Grid. */
const PanelCommand: FC<PanelCommandProps> = ({ command, size }) => {
const [isAnimated, setIsAnimated] = useState(false)
const { svg, isActive, canExecute } = command
const isButtonExecutable = useSelector(state => !canExecute || canExecute(state))
const isButtonActive = useSelector(state => isActive?.(state))
/** Handles the onClick event. Executes the command when tapped. */
const handleTap = useCallback(
(e: React.MouseEvent | React.TouchEvent) => {
if (isButtonExecutable) {
executeCommandWithMulticursor(command, { store, type: 'commandCenter', event: e })
setIsAnimated(true)
// prevent Editable blur
if (isTouch) {
e.preventDefault()
}
}
},
[command, isButtonExecutable],
)
let SVG: React.FC<Icon> | null = null
if (svg) {
SVG = svg as React.FC<Icon>
}
return (
<div
className={css({
display: 'grid',
...(size === 'medium'
? { gridColumn: 'span 2', gridTemplateColumns: '1fr 2fr', gridTemplateAreas: `"command command"` }
: { gridColumn: 'span 1', gridTemplateColumns: 'auto', gridTemplateAreas: `"command"` }),
})}
>
<ActiveButtonGlowImage isActive={isButtonActive} />
<div
aria-label={command.label}
className={cx(
panelCommandRecipe({
isButtonExecutable,
}),
css({ gridArea: 'command' }),
)}
{...fastClick(handleTap)}
>
{SVG && (
<SVG
style={{ justifySelf: 'center' }}
size={size === 'small' ? 24 : size === 'medium' ? 22 : 24}
animated={isAnimated}
animationComplete={() => setIsAnimated(false)}
/>
)}
{!command.hideTitleInPanels && size === 'medium' && (
<div
className={css({
fontSize: '14px',
color: 'fg',
textAlign: 'left',
letterSpacing: '-2%',
})}
>
{command.label}
</div>
)}
</div>
</div>
)
}
export default PanelCommand