feat: vote button reset cntr, replace old votes cntr in some components

This commit is contained in:
MTG2000
2022-05-18 10:33:08 +03:00
parent 7c266bc138
commit c30566cf14
10 changed files with 127 additions and 38 deletions

View File

@@ -19,4 +19,60 @@ export const Default = Template.bind({});
Default.args = {
initVotes: 540,
onVote: () => { }
}
export const Vertical = Template.bind({});
Vertical.args = {
initVotes: 540,
onVote: () => { },
direction: 'vertical'
}
export const Dense = Template.bind({});
Dense.args = {
initVotes: 540,
onVote: () => { },
dense: true
}
export const FillTypeUpdown = Template.bind({});
FillTypeUpdown.args = {
initVotes: 540,
onVote: () => { },
fillType: 'upDown'
}
export const FillTypeBackground = Template.bind({});
FillTypeBackground.args = {
initVotes: 540,
onVote: () => { },
fillType: 'background'
}
export const FillTypeRadial = Template.bind({});
FillTypeRadial.args = {
initVotes: 540,
onVote: () => { },
fillType: 'radial'
}
export const NoCounter = Template.bind({});
NoCounter.args = {
initVotes: 540,
onVote: () => { },
disableCounter: true,
}
export const CounterReset = Template.bind({});
CounterReset.args = {
initVotes: 540,
onVote: () => { },
resetCounterOnRelease: true
}
export const NoShake = Template.bind({});
NoShake.args = {
initVotes: 540,
onVote: () => { },
disableShake: true,
}

View File

@@ -3,10 +3,11 @@ import Button from 'src/Components/Button/Button'
import { useAppSelector, usePressHolder } from 'src/utils/hooks'
import { ComponentProps, useRef, useState } from 'react'
import styles from './styles.module.css'
import { random, randomItem } from 'src/utils/helperFunctions'
import { random, randomItem, numberFormatter } from 'src/utils/helperFunctions'
import { useDebouncedCallback, useThrottledCallback } from '@react-hookz/web'
interface Particle {
id: string,
offsetX: number,
@@ -19,18 +20,24 @@ interface Particle {
type Props = {
initVotes: number,
onVote: (Vote: number) => void,
fillType: 'leftRight' | 'upDown' | "background" | 'radial'
onVote?: (Vote: number) => void,
fillType?: 'leftRight' | 'upDown' | "background" | 'radial',
direction?: 'horizontal' | 'vertical'
disableCounter?: boolean
disableShake?: boolean
dense?: boolean
resetCounterOnRelease?: boolean
} & Omit<ComponentProps<typeof Button>, 'children'>
export default function VoteButton({
disableCounter = false,
disableShake = false,
fillType = 'leftRight',
initVotes,
onVote = () => { },
fillType = 'leftRight',
direction = 'horizontal',
disableCounter = false,
disableShake = false,
dense = false,
resetCounterOnRelease: resetCounterOnReleaseProp = false,
...props }: Props) {
const [voteCnt, setVoteCnt] = useState(0)
const voteCntRef = useRef(0);
@@ -39,12 +46,15 @@ export default function VoteButton({
const [sparks, setSparks] = useState<Particle[]>([]);
const [wasActive, setWasActive] = useState(false);
const [incrementsCount, setIncrementsCount] = useState(0);
const incrementsCountRef = useRef(0);
const totalIncrementsCountRef = useRef(0)
const currentIncrementsCountRef = useRef(0);
const [increments, setIncrements] = useState<Array<{ id: string, value: number }>>([])
const isMobileScreen = useAppSelector(s => s.ui.isMobileScreen);
const resetCounterOnRelease = resetCounterOnReleaseProp;
const doVote = useDebouncedCallback(() => {
onVote(voteCntRef.current);
voteCntRef.current = 0;
@@ -59,9 +69,12 @@ export default function VoteButton({
setBtnShakeClass(s => s === styles.clicked_2 ? styles.clicked_1 : styles.clicked_2)
const _incStep = Math.ceil((incrementsCountRef.current + 1) / 5);
incrementsCountRef.current += 1;
setIncrementsCount(v => incrementsCountRef.current);
const _incStep = Math.ceil((currentIncrementsCountRef.current + 1) / 5);
currentIncrementsCountRef.current += 1;
totalIncrementsCountRef.current += 1;
setIncrementsCount(v => totalIncrementsCountRef.current);
if (!disableCounter)
setIncrements(v => {
@@ -78,7 +91,7 @@ export default function VoteButton({
return newValue;
})
if (incrementsCountRef.current && incrementsCountRef.current % 5 === 0) {
if (totalIncrementsCountRef.current && totalIncrementsCountRef.current % 5 === 0) {
const newSparks = Array(5).fill(0).map((_, idx) => ({
id: (Math.random() + 1).toString(),
offsetX: random(-10, 99),
@@ -104,7 +117,7 @@ export default function VoteButton({
const onHold = useThrottledCallback(clickIncrement, [], 150)
const { onPressDown, onPressUp } = usePressHolder(onHold, 100);
const { onPressDown, onPressUp, isHolding } = usePressHolder(onHold, 100);
const handlePressDown = () => {
setWasActive(true);
@@ -119,6 +132,12 @@ export default function VoteButton({
onPressUp();
onHold();
if (resetCounterOnRelease)
if (!isHolding) {
currentIncrementsCountRef.current = 0;
} else
setTimeout(() =>
currentIncrementsCountRef.current = 0, 150)
}
return (
@@ -145,7 +164,11 @@ export default function VoteButton({
ref={btnContainerRef}
className={`
${styles.btn_content}
relative p-10 rounded-lg text-gray-600 bg-white hover:bg-gray-50
relative rounded-lg text-gray-600 ${!incrementsCount && 'bg-gray-50 hover:bg-gray-100'}
${direction === 'vertical' ?
dense ? "py-4 px-12" : "py-8 px-20"
:
dense ? "py-4 px-8" : "p-8"}
${incrementsCount && "outline"} active:outline outline-1 outline-red-500
${btnShakeClass}
`}
@@ -161,8 +184,15 @@ export default function VoteButton({
`}
>
</div>
<div className={`relative z-10 ${incrementsCount ? "text-red-600" : "text-gray-600"}`}>
<MdLocalFireDepartment className='' /><span className="align-middle"> {initVotes + voteCnt}</span>
<div className={`
relative z-10
${incrementsCount ? "text-red-800" : "text-gray-600"}
flex justify-center items-center gap-8 text-left ${direction === 'vertical' && "flex-col !text-center"}
`}>
<MdLocalFireDepartment
className={`text-body2 ${incrementsCount ? "text-red-600" : "text-red-600"}`}
/><span className="align-middle w-[4ch]"> {numberFormatter(initVotes + voteCnt)}</span>
</div>
</div>
{increments.map(increment => <span

View File

@@ -8,11 +8,11 @@
}
.btn_content.clicked_1 {
animation: shake_1 0.2s 1 ease-out;
animation: shake_1 0.14s 1 ease-in-out;
}
/* Same animation, two classes so that the animation restarts between clicks */
.btn_content.clicked_2 {
animation: shake_2 0.2s 1 ease-out;
animation: shake_2 0.14s 1 ease-in-out;
}
@keyframes shake_1 {
@@ -21,11 +21,11 @@
}
33% {
transform: rotate(calc(clamp(10, var(--increments) / 2, 30) * 1deg));
transform: rotate(calc(clamp(5, var(--increments) / 3, 20) * 1deg));
}
66% {
transform: rotate(calc(-1 * clamp(10, var(--increments) / 2, 30) * 1deg));
transform: rotate(calc(-1 * clamp(5, var(--increments) / 2, 20) * 1deg));
}
100% {

View File

@@ -5,6 +5,7 @@ import { FiUsers } from "react-icons/fi"
import Badge from "src/Components/Badge/Badge"
import Button from "src/Components/Button/Button"
import { Link } from "react-router-dom"
import VoteButton from "src/Components/VoteButton/VoteButton"
export type BountyCardType = Pick<Bounty,
| 'id'
@@ -59,8 +60,8 @@ export default function BountyCard({ bounty }: Props) {
</div>
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24">
<VotesCount count={bounty.votes_count} />
<div className="flex gap-24 items-center">
<VoteButton initVotes={bounty.votes_count} dense />
<div className="text-gray-600">
<FiUsers /> <span className="align-middle text-body5">{bounty.applicants_count} Applicants</span>
</div>

View File

@@ -5,6 +5,7 @@ import { FiUsers } from "react-icons/fi"
import Badge from "src/Components/Badge/Badge"
import { Link } from "react-router-dom"
import { trimText } from "src/utils/helperFunctions"
import VoteButton from "src/Components/VoteButton/VoteButton"
export type QuestionCardType = Pick<Question,
| 'id'
@@ -50,8 +51,8 @@ export default function QuestionCard({ question }: Props) {
</div>
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24">
<VotesCount count={question.votes_count} />
<div className="flex gap-24 items-center">
<VoteButton initVotes={question.votes_count} dense />
<div className="text-gray-600">
<FiUsers /> <span className="align-middle text-body5">{question.answers_count} Answers</span>
</div>

View File

@@ -3,6 +3,7 @@ import { Story } from "src/features/Posts/types"
import Header from "../Header/Header"
import { BiComment } from 'react-icons/bi'
import { Link } from "react-router-dom"
import VoteButton from "src/Components/VoteButton/VoteButton"
export type StoryCardType = Pick<Story,
| 'id'
@@ -31,8 +32,8 @@ export default function StoryCard({ story }: Props) {
<p className="text-body4 text-gray-600 mt-8">{story.excerpt}</p>
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24">
<VotesCount count={story.votes_count} />
<div className="flex gap-24 items-center">
<VoteButton initVotes={story.votes_count} dense />
<div className="text-gray-600">
<BiComment /> <span className="align-middle text-body5">{story.comments_count} Comments</span>
</div>

View File

@@ -8,6 +8,7 @@ import VotesCount from "src/Components/VotesCount/VotesCount";
import Button from "src/Components/Button/Button";
import { FiGithub, FiShare2 } from "react-icons/fi";
import BountyApplicants from "./BountyApplicants";
import VoteButton from "src/Components/VoteButton/VoteButton";
interface Props {
@@ -26,8 +27,8 @@ export default function BountyPageContent({ bounty }: Props) {
<span className="text-body4 text-gray-600 font-bolder">Reward: </span>
<span className="text-body4 text-purple-500 font-medium">{bounty.reward_amount} sats</span>
</div>
<div className="flex gap-24">
<VotesCount count={bounty.votes_count} />
<div className="flex gap-24 items-center">
<VoteButton initVotes={bounty.votes_count} />
<div className="text-black font-medium">
<BiComment /> <span className="align-middle text-body5">32 Comments</span>
</div>

View File

@@ -1,16 +1,12 @@
import { BsBookmark } from "react-icons/bs"
import { MdIosShare, MdLocalFireDepartment } from "react-icons/md"
import VoteButton from "src/Components/VoteButton/VoteButton"
export default function PostActions() {
const actions = [
{
icon: MdLocalFireDepartment,
value: 123,
classes: ''
},
{
icon: BsBookmark,
value: 27
@@ -23,10 +19,11 @@ export default function PostActions() {
return (
<ul className="bg-white rounded-12 p-16 border flex justify-around md:flex-col gap-32">
<VoteButton initVotes={123} direction='vertical' fillType="upDown" />
{actions.map((action, idx) => <li
className={`py-4 px-16 text-body5 flex flex-col items-center cursor-pointer rounded-24
${idx === 0 ? 'bg-warning-50 hover:bg-warning-100 active:bg-warning-200 text-gray-900 font-medium' : 'text-gray-500 hover:bg-gray-50 active:bg-gray-100'}`}>
<action.icon className={idx === 0 ? "text-fire text-body4 scale-125 mb-4" : "text-body4 mb-8"}></action.icon>
className={`py-8 px-20 text-body5 flex flex-col justify-center items-center cursor-pointer rounded-8
${'text-gray-500 hover:bg-gray-50 active:bg-gray-100'}`}>
<action.icon className={"text-body4 mb-8"}></action.icon>
<span>{action.value}</span>
</li>)}
</ul>

View File

@@ -43,7 +43,6 @@ export default function PostDetailsPage() {
style={{
top: `${navHeight + 16}px`,
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
overflowY: "scroll",
}}>
<PostActions />
</div>

View File

@@ -1,4 +1,4 @@
import { useRef } from "react";
import { useRef, useState } from "react";
export const usePressHolder = (onHold: () => any, holdThreshold: number = 400) => {
@@ -7,6 +7,7 @@ export const usePressHolder = (onHold: () => any, holdThreshold: number = 400) =
timerID: 0,
previousTimestamp: -1
});
const [isHolding, setIsHolding] = useState(false)
const onPressDown = () => {
ref.current.timerID = requestAnimationFrame(timer)
@@ -18,6 +19,7 @@ export const usePressHolder = (onHold: () => any, holdThreshold: number = 400) =
cancelAnimationFrame(ref.current.timerID);
ref.current.cntr = 0;
ref.current.previousTimestamp = -1;
setIsHolding(false)
}
function timer(timestamp: number) {
@@ -30,11 +32,12 @@ export const usePressHolder = (onHold: () => any, holdThreshold: number = 400) =
ref.current.cntr += dt;
} else {
onHold();
setIsHolding(true)
}
ref.current.timerID = requestAnimationFrame(timer);
}
return { onPressUp, onPressDown }
return { onPressUp, onPressDown, isHolding }
}