update: show loading & success state on vote-btn, fix multiline bug on story-form title input

This commit is contained in:
MTG2000
2022-07-15 12:21:11 +03:00
parent f6b381ec23
commit 04397351bd
4 changed files with 96 additions and 18 deletions

View File

@@ -1,5 +1,6 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { centerDecorator } from 'src/utils/storybook/decorators';
import { ComponentProps } from 'react'
import VoteButton from './VoteButton';
@@ -14,65 +15,71 @@ export default {
const Template: ComponentStory<typeof VoteButton> = (args) => <VoteButton {...args} />;
const onVoteHandler: ComponentProps<typeof VoteButton>['onVote'] = (a, c) => {
setTimeout(() => {
c.onSuccess?.();
c.onSetteled?.();
}, 2000)
}
export const Default = Template.bind({});
Default.args = {
votes: 540,
onVote: () => { }
onVote: onVoteHandler
}
export const Vertical = Template.bind({});
Vertical.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
direction: 'vertical'
}
export const Dense = Template.bind({});
Dense.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
dense: true
}
export const FillTypeUpdown = Template.bind({});
FillTypeUpdown.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
fillType: 'upDown'
}
export const FillTypeBackground = Template.bind({});
FillTypeBackground.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
fillType: 'background'
}
export const FillTypeRadial = Template.bind({});
FillTypeRadial.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
fillType: 'radial'
}
export const NoCounter = Template.bind({});
NoCounter.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
disableCounter: true,
}
export const CounterReset = Template.bind({});
CounterReset.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
resetCounterOnRelease: true
}
export const NoShake = Template.bind({});
NoShake.args = {
votes: 540,
onVote: () => { },
onVote: onVoteHandler,
disableShake: true,
}

View File

@@ -7,6 +7,8 @@ import { random, randomItem, numberFormatter } from 'src/utils/helperFunctions'
import { useDebouncedCallback, useMountEffect, useThrottledCallback } from '@react-hookz/web'
import { UnionToObjectKeys } from 'src/utils/types/utils'
import { Portal } from '../Portal/Portal'
import { ThreeDots } from 'react-loader-spinner'
import { AnimatePresence, motion } from 'framer-motion'
@@ -22,7 +24,11 @@ interface Particle {
type Props = {
votes: number,
onVote?: (amount: number, config: Partial<{ onSetteled: () => void }>) => void,
onVote?: (amount: number, config: Partial<{
onSetteled: () => void;
onError: () => void;
onSuccess: () => void;
}>) => void,
fillType?: 'leftRight' | 'upDown' | "background" | 'radial',
direction?: 'horizontal' | 'vertical'
disableCounter?: boolean
@@ -44,6 +50,8 @@ const btnPadding: UnionToObjectKeys<Props, 'direction', any> = {
} as UnionToObjectKeys<Props, 'size'>
}
type BtnState = 'ready' | 'voting' | 'loading' | "success" | "fail";
export default function VoteButton({
votes,
onVote = () => { },
@@ -64,17 +72,26 @@ export default function VoteButton({
const totalIncrementsCountRef = useRef(0)
const currentIncrementsCountRef = useRef(0);
const [increments, setIncrements] = useState<Array<{ id: string, value: number }>>([]);
const [btnPosition, setBtnPosition] = useState<{ top: number, left: number, width: number, height: number }>()
const [btnPosition, setBtnPosition] = useState<{ top: number, left: number, width: number, height: number }>();
const [btnState, setBtnState] = useState<BtnState>('ready');
const isMobileScreen = useAppSelector(s => s.ui.isMobileScreen);
const resetCounterOnRelease = resetCounterOnReleaseProp;
const doVote = useDebouncedCallback(() => {
setBtnState('loading');
const amount = voteCntRef.current;
onVote(amount, { onSetteled: () => setVoteCnt(v => v - amount) });
onVote(amount, {
onSuccess: () => setBtnState("success"),
onError: () => setBtnState('fail'),
onSetteled: () => {
setVoteCnt(v => v - amount);
setTimeout(() => setBtnState("ready"), 2000);
}
});
voteCntRef.current = 0;
}, [], 2000)
}, [], 1500)
const clickIncrement = () => {
if (!disableShake)
@@ -132,13 +149,14 @@ export default function VoteButton({
const { onPressDown, onPressUp, isHolding } = usePressHolder(onHold, 100);
const handlePressDown = () => {
setWasActive(true);
if (btnState !== 'ready' && btnState !== 'voting') return;
setBtnState('voting');
onPressDown();
}
const handlePressUp = (event?: any) => {
if (!wasActive) return;
setWasActive(false);
if (btnState !== 'voting') return;
if (event?.preventDefault) event.preventDefault();
@@ -230,6 +248,26 @@ export default function VoteButton({
/><span className="align-middle w-[4ch]"> {numberFormatter(votes + voteCnt)}</span>
</div>
<AnimatePresence>
{btnState === 'loading' &&
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={styles.loading}>
<ThreeDots width={20} color="#dc2626" />
</motion.div>
}
{btnState === 'success' &&
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={styles.success}>
Thanks!!
</motion.div>
}
</AnimatePresence>
</div>
<Portal id='effects-container'>

View File

@@ -106,6 +106,38 @@
);
}
.loading {
pointer-events: none;
position: absolute;
border-radius: inherit;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #f9f6f6;
font-size: 14px;
color: #dc2626;
z-index: 10;
}
.success {
pointer-events: none;
position: absolute;
border-radius: inherit;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #f9f6f6;
font-weight: 600;
font-size: 14px;
color: #dc2626;
outline: 1px solid #ef4444;
z-index: 11;
}
@keyframes fly_value {
0% {
transform: translate(-50%, 0) scale(0.5);

View File

@@ -45,7 +45,7 @@ export default function StoryForm(props: Props) {
useEffect(() => {
if (editMode)
titleInputRef.current?.setAttribute("style", "height:" + (titleInputRef.current.scrollHeight) + "px;overflow-y:hidden;");
setTimeout(() => titleInputRef.current?.setAttribute("style", "height:" + (titleInputRef.current.scrollHeight) + "px;overflow-y:hidden;"), 0)
}, [editMode])
@@ -133,8 +133,9 @@ export default function StoryForm(props: Props) {
<div className="mt-16 relative">
<textarea
rows={1}
autoFocus
className="p-0 text-[42px] leading-[58px] border-0 max-w-full
className="p-0 text-[42px] leading-[58px] border-0 w-full max-w-full resize-none
focus:border-0 focus:outline-none focus:ring-0 font-bolder placeholder:!text-gray-400"
placeholder='New story title here...'
{...titleRegisteration}