From 04397351bdae17d9cdbbc64397ffacbad012ae3e Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Fri, 15 Jul 2022 12:21:11 +0300 Subject: [PATCH] update: show loading & success state on vote-btn, fix multiline bug on story-form title input --- .../VoteButton/VoteButton.stories.tsx | 25 +++++---- src/Components/VoteButton/VoteButton.tsx | 52 ++++++++++++++++--- src/Components/VoteButton/styles.module.scss | 32 ++++++++++++ .../Components/StoryForm/StoryForm.tsx | 5 +- 4 files changed, 96 insertions(+), 18 deletions(-) diff --git a/src/Components/VoteButton/VoteButton.stories.tsx b/src/Components/VoteButton/VoteButton.stories.tsx index d91a61f..c5108bf 100644 --- a/src/Components/VoteButton/VoteButton.stories.tsx +++ b/src/Components/VoteButton/VoteButton.stories.tsx @@ -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 = (args) => ; +const onVoteHandler: ComponentProps['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, } \ No newline at end of file diff --git a/src/Components/VoteButton/VoteButton.tsx b/src/Components/VoteButton/VoteButton.tsx index 20a4520..2bff3bd 100644 --- a/src/Components/VoteButton/VoteButton.tsx +++ b/src/Components/VoteButton/VoteButton.tsx @@ -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 = { } as UnionToObjectKeys } +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>([]); - 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('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({ /> {numberFormatter(votes + voteCnt)} + + {btnState === 'loading' && + + + + } + {btnState === 'success' && + + Thanks!! + + } + diff --git a/src/Components/VoteButton/styles.module.scss b/src/Components/VoteButton/styles.module.scss index a7e116d..2d0b3c9 100644 --- a/src/Components/VoteButton/styles.module.scss +++ b/src/Components/VoteButton/styles.module.scss @@ -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); diff --git a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx index 98f6c2c..f5d6f17 100644 --- a/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx +++ b/src/features/Posts/pages/CreatePostPage/Components/StoryForm/StoryForm.tsx @@ -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) {