| 1 |
import { getOption } from '@agent/lib/wp'; |
| 2 |
import { |
| 3 |
__experimentalToggleGroupControl as ToggleGroupControl, |
| 4 |
__experimentalToggleGroupControlOption as ToggleGroupControlOption, |
| 5 |
} from '@wordpress/components'; |
| 6 |
import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; |
| 7 |
import { __, _x } from '@wordpress/i18n'; |
| 8 |
import { |
| 9 |
flipVertical, |
| 10 |
Icon, |
| 11 |
notAllowed, |
| 12 |
shadow, |
| 13 |
square, |
| 14 |
} from '@wordpress/icons'; |
| 15 |
import classNames from 'classnames'; |
| 16 |
|
| 17 |
const animations = [ |
| 18 |
{ |
| 19 |
// translators: Animation style where the element gradually appears by transitioning from transparent to visible. Translate as a simple, non-technical description of the visual effect (e.g., 'appear', 'show up') suitable for non-technical users. |
| 20 |
name: _x('Fade In', 'animation type', 'extendify-local'), |
| 21 |
slug: 'fade', |
| 22 |
icon: shadow, |
| 23 |
}, |
| 24 |
{ |
| 25 |
// translators: Animation style where the element appears while moving upward. Translate as a simple, non-technical description of the motion (e.g., 'appear from below', 'rise into view') suitable for non-technical users. |
| 26 |
name: _x('Fade Up', 'animation type', 'extendify-local'), |
| 27 |
slug: 'fade-up', |
| 28 |
icon: flipVertical, |
| 29 |
}, |
| 30 |
{ |
| 31 |
// translators: Animation style where the element appears by scaling from small to its full size. Translate as a simple, non-technical description of the effect (e.g., 'zoom in', 'get closer') suitable for non-technical users. If the target language commonly uses the English term 'zoom', it's acceptable to keep it. |
| 32 |
name: _x('Zoom In', 'animation type', 'extendify-local'), |
| 33 |
slug: 'zoom-in', |
| 34 |
icon: square, |
| 35 |
}, |
| 36 |
{ |
| 37 |
// translators: None refers to 'no animation'. Apply the correct grammatical gender/form that agrees with the word for 'animation' in the target language. |
| 38 |
name: _x('None', 'animation type', 'extendify-local'), |
| 39 |
slug: 'none', |
| 40 |
icon: notAllowed, |
| 41 |
}, |
| 42 |
]; |
| 43 |
|
| 44 |
const speeds = [ |
| 45 |
{ |
| 46 |
// translators: "Slow" refers to a slower speed at which the animation effect occurs. This is an option in a list of animation speeds. |
| 47 |
name: _x('Slow', 'animation speed', 'extendify-local'), |
| 48 |
slug: 'slow', |
| 49 |
}, |
| 50 |
{ |
| 51 |
// translators: "Medium" refers to a moderate speed at which the animation effect occurs. This is an option in a list of animation speeds. |
| 52 |
name: _x('Medium', 'animation speed', 'extendify-local'), |
| 53 |
slug: 'medium', |
| 54 |
}, |
| 55 |
{ |
| 56 |
// translators: "Fast" refers to a quicker speed at which the animation effect occurs. This is an option in a list of animation speeds. |
| 57 |
name: _x('Fast', 'animation speed', 'extendify-local'), |
| 58 |
slug: 'fast', |
| 59 |
}, |
| 60 |
]; |
| 61 |
|
| 62 |
export const SelectAnimation = ({ onConfirm, onCancel }) => { |
| 63 |
const initialSettings = useRef({}); |
| 64 |
const [animation, setAnimation] = useState(null); |
| 65 |
const [touched, setTouched] = useState(0); |
| 66 |
const [speed, setSpeed] = useState(null); |
| 67 |
const [loading, setLoading] = useState(true); |
| 68 |
const undoAnimation = useCallback(() => { |
| 69 |
if (!touched) return; |
| 70 |
window.ExtendableAnimations?.setType(initialSettings.current.type); |
| 71 |
window.ExtendableAnimations?.setSpeed(initialSettings.current.speed); |
| 72 |
}, [touched]); |
| 73 |
|
| 74 |
const confirmed = useRef(false); |
| 75 |
// Ref keeps the latest undo function, so clean-up always sees the current `touched` state. |
| 76 |
const undoRef = useRef(undoAnimation); |
| 77 |
undoRef.current = undoAnimation; |
| 78 |
useEffect(() => { |
| 79 |
return () => { |
| 80 |
if (!confirmed.current) undoRef.current(); |
| 81 |
}; |
| 82 |
}, []); |
| 83 |
|
| 84 |
const handleConfirm = () => { |
| 85 |
if (!animation || !speed || loading) return; |
| 86 |
confirmed.current = true; |
| 87 |
onConfirm({ data: { type: animation, speed } }); |
| 88 |
}; |
| 89 |
|
| 90 |
useEffect(() => { |
| 91 |
if (!touched) return; |
| 92 |
window.ExtendableAnimations.setType(animation); |
| 93 |
window.ExtendableAnimations.setSpeed(speed); |
| 94 |
}, [speed, animation, touched]); |
| 95 |
|
| 96 |
useEffect(() => { |
| 97 |
if (!loading) return; |
| 98 |
getOption('extendify_animation_settings').then((settings = {}) => { |
| 99 |
const defaults = { type: 'none', speed: 'medium' }; |
| 100 |
initialSettings.current = { ...defaults, ...(settings || {}) }; |
| 101 |
setAnimation(initialSettings.current.type); |
| 102 |
setSpeed(initialSettings.current.speed); |
| 103 |
setLoading(false); |
| 104 |
}); |
| 105 |
}, []); |
| 106 |
|
| 107 |
if (loading) { |
| 108 |
return ( |
| 109 |
<div className="min-h-24 p-2 text-center text-sm"> |
| 110 |
{ |
| 111 |
// translators: This is a status message. 'Loading' is a verb — the app is currently loading the animation options. It is not referring to 'loading animation' as a compound noun. |
| 112 |
__('Loading animation options...', 'extendify-local') |
| 113 |
} |
| 114 |
</div> |
| 115 |
); |
| 116 |
} |
| 117 |
|
| 118 |
return ( |
| 119 |
<div className="mb-4 ms-2 me-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50"> |
| 120 |
<div className="rounded-lg p-3 border-b border-gray-300 bg-white"> |
| 121 |
<div className="text-xs uppercase mb-3 text-gray-700 font-medium"> |
| 122 |
{/* translators: "Type" refers to the category of animation effects available. e.g. The type could be 'Zoom In', 'Fade', etc. */} |
| 123 |
{_x('Type', 'animation type', 'extendify-local')} |
| 124 |
</div> |
| 125 |
<div className="grid gap-2 mb-6 grid-cols-2 auto-rows-fr"> |
| 126 |
{animations.map(({ name, slug, icon }) => ( |
| 127 |
<Button |
| 128 |
key={slug} |
| 129 |
name={name} |
| 130 |
icon={icon} |
| 131 |
selected={animation === slug} |
| 132 |
onClick={() => { |
| 133 |
setTouched((v) => v + 1); |
| 134 |
setAnimation(slug); |
| 135 |
}} |
| 136 |
/> |
| 137 |
))} |
| 138 |
</div> |
| 139 |
{/* mb-1 because the toggle group has margins even if undefined */} |
| 140 |
<div className="text-xs uppercase mb-1 text-gray-700 font-medium"> |
| 141 |
{/* translators: "Speed" refers to the speed at which the animation effect occurs. This is a heading label shown above the toggle group for selecting animation speed. */} |
| 142 |
{_x('Speed', 'animation speed', 'extendify-local')} |
| 143 |
</div> |
| 144 |
<ToggleGroupControl |
| 145 |
className="border-gray-300 rounded-sm m-0 before:rounded-sm focus-within:border-gray-900" |
| 146 |
__next40pxDefaultSize |
| 147 |
__nextHasNoMarginBottom |
| 148 |
isBlock |
| 149 |
value={speed} |
| 150 |
onChange={(speed) => { |
| 151 |
setTouched((v) => v + 1); |
| 152 |
setSpeed(speed); |
| 153 |
}} |
| 154 |
> |
| 155 |
{speeds.map(({ name, slug }) => ( |
| 156 |
<ToggleGroupControlOption key={slug} label={name} value={slug} /> |
| 157 |
))} |
| 158 |
</ToggleGroupControl> |
| 159 |
</div> |
| 160 |
<div className="flex justify-start gap-2 p-3"> |
| 161 |
<button |
| 162 |
type="button" |
| 163 |
className="w-full rounded-sm border border-gray-500 bg-white p-2 text-sm text-gray-900" |
| 164 |
onClick={onCancel} |
| 165 |
> |
| 166 |
{__('Cancel', 'extendify-local')} |
| 167 |
</button> |
| 168 |
<button |
| 169 |
type="button" |
| 170 |
className="w-full rounded-sm border border-design-main bg-design-main p-2 text-sm text-white" |
| 171 |
disabled={!animation || !speed || loading} |
| 172 |
onClick={handleConfirm} |
| 173 |
> |
| 174 |
{__('Save', 'extendify-local')} |
| 175 |
</button> |
| 176 |
</div> |
| 177 |
</div> |
| 178 |
); |
| 179 |
}; |
| 180 |
|
| 181 |
const Button = ({ name, selected, icon, onClick }) => { |
| 182 |
return ( |
| 183 |
<button |
| 184 |
type="button" |
| 185 |
className={classNames( |
| 186 |
'relative h-full w-full rounded-lg border p-2 text-sm text-left flex flex-col gap-4 font-medium hover:bg-gray-100', |
| 187 |
{ |
| 188 |
'border-gray-900 bg-gray-100': selected, |
| 189 |
'border-gray-300 bg-gray-50': !selected, |
| 190 |
}, |
| 191 |
)} |
| 192 |
onClick={onClick} |
| 193 |
> |
| 194 |
<Icon icon={icon} className="h-6 w-6" /> |
| 195 |
{name} |
| 196 |
{selected && ( |
| 197 |
<div className="absolute top-1 right-1 h-3.5 w-3.5 flex items-center justify-center rounded-full bg-design-main text-center text-xss text-white leading-none"> |
| 198 |
<span>✓</span> |
| 199 |
</div> |
| 200 |
)} |
| 201 |
</button> |
| 202 |
); |
| 203 |
}; |
| 204 |
|