mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-02-19 21:44:20 +01:00
feat: vote button reset cntr, replace old votes cntr in some components
This commit is contained in:
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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% {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -43,7 +43,6 @@ export default function PostDetailsPage() {
|
||||
style={{
|
||||
top: `${navHeight + 16}px`,
|
||||
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
|
||||
overflowY: "scroll",
|
||||
}}>
|
||||
<PostActions />
|
||||
</div>
|
||||
|
||||
@@ -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 }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user