Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions components/brain/left-sidebar/waves/MemesWaveFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import MemesWaveQuickVoteTrigger from "@/components/brain/left-sidebar/waves/MemesWaveQuickVoteTrigger";
import MemesWaveZapIcon from "@/components/brain/left-sidebar/waves/MemesWaveZapIcon";
import { useMemesWaveFooterStats } from "@/hooks/useMemesWaveFooterStats";
import { formatNumberWithCommas } from "@/helpers/Helpers";
import { AnimatePresence, motion } from "framer-motion";
import { BoltIcon } from "@heroicons/react/24/solid";
import React from "react";

interface MemesWaveFooterProps {
Expand Down Expand Up @@ -53,7 +53,7 @@ const MemesWaveFooter: React.FC<MemesWaveFooterProps> = ({
className={
collapsed
? "tw-z-10 tw-flex tw-flex-shrink-0 tw-justify-center tw-px-2 tw-pb-2 tw-pt-1"
: "tw-z-10 tw-flex-shrink-0 tw-bg-gradient-to-t tw-from-iron-950 tw-via-iron-950/95 tw-to-transparent tw-px-4 tw-pb-4 tw-pt-6"
: "tw-relative tw-z-20 tw-mt-auto tw-flex-shrink-0"
}
>
{collapsed ? (
Expand All @@ -67,27 +67,37 @@ const MemesWaveFooter: React.FC<MemesWaveFooterProps> = ({
type="button"
aria-label={`Uncast Power, ${formatNumberWithCommas(
uncastPower
)} ${votingLabel ?? "Votes"}, ${unratedCount} left`}
)} ${votingLabel ?? "Votes"} left, ${formatNumberWithCommas(
unratedCount
)} unexplored`}
onClick={handleOpenQuickVote}
onFocus={handlePrefetchQuickVote}
onMouseEnter={handlePrefetchQuickVote}
className="tw-flex tw-w-full tw-items-center tw-justify-between tw-gap-x-4 tw-rounded-2xl tw-border tw-border-solid tw-border-primary-500/30 tw-bg-iron-900/95 tw-px-4 tw-py-3 tw-text-left tw-shadow-[0_18px_36px_rgba(0,0,0,0.28)] tw-backdrop-blur-sm tw-transition-colors tw-duration-300 desktop-hover:hover:tw-border-primary-400/40 desktop-hover:hover:tw-bg-iron-900"
className="tw-group tw-mt-auto tw-w-full tw-flex-shrink-0 tw-cursor-pointer tw-border-0 tw-border-t tw-border-solid tw-border-iron-800/60 tw-bg-black tw-p-4 tw-text-left tw-transition-colors desktop-hover:hover:tw-bg-iron-900/40"
>
<div className="tw-min-w-0 tw-flex-1">
<div className="tw-text-xs tw-font-medium tw-uppercase tw-tracking-[0.08em] tw-text-iron-500">
Uncast Power
</div>
<div className="tw-mt-1 tw-flex tw-items-center tw-gap-x-2 tw-text-primary-300">
<BoltIcon className="tw-size-4 tw-flex-shrink-0" />
<span className="tw-truncate tw-text-lg tw-font-semibold">
{formatNumberWithCommas(uncastPower)}
{votingLabel ? ` ${votingLabel}` : ""}
<div className="tw-relative tw-flex tw-items-center tw-justify-between tw-gap-4 tw-overflow-hidden tw-rounded-xl tw-border tw-border-solid tw-border-[#2d3753] tw-bg-[#0c1018] tw-px-4 tw-py-2.5 tw-shadow-lg tw-transition-all tw-duration-200 desktop-hover:group-hover:tw-border-[#3a4670] desktop-hover:group-hover:tw-bg-[#0f1420]">
<span
aria-hidden="true"
className="tw-pointer-events-none tw-absolute tw-inset-0 -tw-translate-x-full tw-bg-gradient-to-r tw-from-white/0 tw-via-white/[0.08] tw-to-white/0 tw-opacity-50 tw-transition-transform tw-duration-1000 tw-ease-out desktop-hover:group-hover:tw-translate-x-full"
/>
<div className="tw-relative tw-z-10 tw-flex tw-min-w-0 tw-flex-col tw-gap-1.5">
<span className="tw-text-[10px] tw-font-bold tw-uppercase tw-tracking-widest tw-text-[#6b7c93]">
Uncast votes
</span>

<div className="tw-flex tw-items-center tw-gap-2">
<MemesWaveZapIcon className="tw-size-4 tw-flex-shrink-0 tw-fill-primary-400/20 tw-text-primary-400" />
<span className="tw-truncate tw-text-sm tw-font-semibold tw-tracking-tight tw-text-white">
{formatNumberWithCommas(uncastPower)}
{votingLabel ? ` ${votingLabel}` : " votes"}
</span>
</div>
</div>

<span className="tw-relative tw-z-10 tw-text-xs tw-font-semibold tw-text-[#8199ea] tw-shadow-sm">
{formatNumberWithCommas(unratedCount)} unexplored
</span>
</div>
<span className="tw-flex-shrink-0 tw-rounded-xl tw-border tw-border-solid tw-border-primary-500/20 tw-bg-primary-500/10 tw-px-3 tw-py-2 tw-text-sm tw-font-semibold tw-text-primary-300">
{formatNumberWithCommas(unratedCount)} left
</span>
</button>
)}
</motion.div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import MemesWaveZapIcon from "@/components/brain/left-sidebar/waves/MemesWaveZapIcon";
import { formatNumberWithCommas } from "@/helpers/Helpers";
import { BoltIcon } from "@heroicons/react/24/solid";
import React from "react";

interface MemesWaveQuickVoteTriggerProps {
Expand Down Expand Up @@ -33,7 +33,7 @@ const MemesWaveQuickVoteTrigger: React.FC<MemesWaveQuickVoteTriggerProps> = ({
className ?? ""
}`}
>
<BoltIcon className="tw-size-4 tw-flex-shrink-0" />
<MemesWaveZapIcon className="tw-size-4 tw-flex-shrink-0 tw-fill-primary-300/20" />
<span className="tw-text-xs tw-font-semibold">
{formatNumberWithCommas(unratedCount)}
</span>
Expand Down
28 changes: 28 additions & 0 deletions components/brain/left-sidebar/waves/MemesWaveZapIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import React from "react";

type MemesWaveZapIconProps = React.SVGProps<SVGSVGElement>;

export default function MemesWaveZapIcon({
className,
...rest
}: MemesWaveZapIconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="currentColor"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
{...rest}
>
<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" />
</svg>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"use client";

import MemesWaveZapIcon from "@/components/brain/left-sidebar/waves/MemesWaveZapIcon";
import { formatNumberWithCommas } from "@/helpers/Helpers";
import useHasTouchInput from "@/hooks/useHasTouchInput";
import clsx from "clsx";
import { useEffect, useRef } from "react";
import MemesQuickVoteCustomAmountRow from "./MemesQuickVoteCustomAmountRow";
import MemesQuickVoteQuickAmountsRow from "./MemesQuickVoteQuickAmountsRow";

type VoteFeedbackSource = "custom-submit" | "quick-amount";

interface MemesQuickVoteActionBarProps {
readonly customValue: string;
readonly feedbackAmount: number | null;
readonly feedbackSource: VoteFeedbackSource | null;
readonly isCustomOpen: boolean;
readonly isSubmitting: boolean;
readonly isVoteFeedbackActive: boolean;
readonly latestUsedAmount: number | null;
readonly quickAmounts: readonly number[];
readonly uncastPower: number | null;
readonly votingLabel: string | null;
readonly onCustomChange: (value: string) => void;
readonly onCustomSubmit: () => void;
readonly onOpenCustom: () => void;
readonly onSkip: () => void;
readonly onVoteAmount: (amount: number) => void;
}

export default function MemesQuickVoteActionBar({
customValue,
feedbackAmount,
feedbackSource,
isCustomOpen,
isSubmitting,
isVoteFeedbackActive,
latestUsedAmount,
quickAmounts,
uncastPower,
votingLabel,
onCustomChange,
onCustomSubmit,
onOpenCustom,
onSkip,
onVoteAmount,
}: MemesQuickVoteActionBarProps) {
const hasTouchInput = useHasTouchInput();
const hasQuickAmounts = quickAmounts.length > 0;
const isCustomRowVisible = !hasQuickAmounts || isCustomOpen;
const customInputRef = useRef<HTMLInputElement | null>(null);
const previousCustomRowVisibleRef = useRef(isCustomRowVisible);
const customAmountLabel =
customValue.trim().length > 0 && Number.parseInt(customValue, 10) > 0
? formatNumberWithCommas(Number.parseInt(customValue, 10))
: null;
Comment thread
ragnep marked this conversation as resolved.

useEffect(() => {
const wasCustomRowVisible = previousCustomRowVisibleRef.current;
previousCustomRowVisibleRef.current = isCustomRowVisible;

const justOpenedCustomRow = !wasCustomRowVisible && isCustomRowVisible;

if (
hasTouchInput ||
!hasQuickAmounts ||
!justOpenedCustomRow ||
isSubmitting
) {
return;
}

customInputRef.current?.focus();
}, [hasQuickAmounts, hasTouchInput, isCustomRowVisible, isSubmitting]);

const handleToggleCustom = () => {
if (isSubmitting) {
return;
}

onOpenCustom();
};

return (
<div className="tw-relative tw-bg-[linear-gradient(180deg,rgba(10,10,12,0.68),rgba(10,10,12,0.94))] tw-px-3 tw-pb-[calc(env(safe-area-inset-bottom,0px)+0.375rem)] tw-pt-1.5 tw-backdrop-blur-[28px] sm:tw-pb-[calc(env(safe-area-inset-bottom,0px)+0.5rem)] sm:tw-pt-2 md:tw-relative md:tw-z-20 md:tw-shrink-0 md:tw-p-0 md:tw-px-8 md:tw-pb-6 md:tw-pt-0">
<div className="tw-relative tw-z-10 tw-flex tw-flex-col tw-gap-2 sm:tw-gap-3">
<div className="tw-flex tw-flex-col tw-gap-2 tw-rounded-xl tw-border tw-border-solid tw-border-iron-900 tw-bg-iron-950 tw-p-3 tw-shadow-[inset_0_1px_0_rgba(255,255,255,0.04),0_12px_28px_rgba(0,0,0,0.22)] sm:tw-gap-3 sm:tw-p-4 md:tw-shadow-[0_20px_40px_rgba(0,0,0,0.18)]">
<div className="tw-flex tw-flex-wrap tw-items-center tw-justify-between tw-gap-1.5 tw-px-0.5 sm:tw-gap-2 sm:tw-px-1 md:tw-flex-nowrap">
<p className="tw-mb-0 tw-text-[11px] tw-font-bold tw-uppercase tw-tracking-widest tw-text-iron-500">
Quick Vote
</p>

{typeof uncastPower === "number" && (
<div className="tw-inline-flex tw-items-center tw-gap-1.5 tw-px-0.5 tw-py-0.5 tw-text-iron-300 sm:tw-gap-2 sm:tw-py-1">
<MemesWaveZapIcon className="tw-size-3.5 tw-flex-shrink-0 tw-fill-primary-400/20 tw-text-primary-400" />
<span className="tw-text-xs tw-font-bold tw-tracking-wide">
<span className="tw-text-iron-300">
{formatNumberWithCommas(uncastPower)}{" "}
{votingLabel ?? "votes"} remaining
</span>
</span>
</div>
)}
</div>

<div className="tw-relative tw-h-11 tw-overflow-hidden sm:tw-h-12 md:tw-h-12">
{hasQuickAmounts && (
<div
aria-hidden={isCustomOpen}
className={clsx(
"tw-absolute tw-inset-0 tw-h-full tw-w-full tw-transform-gpu tw-transition-all tw-duration-300 tw-ease-out motion-reduce:tw-transform-none motion-reduce:tw-transition-none",
isCustomOpen
? "tw-pointer-events-none tw-z-0 tw-scale-[0.97] tw-opacity-0"
: "tw-z-10 tw-scale-100 tw-opacity-100"
)}
>
<MemesQuickVoteQuickAmountsRow
feedbackAmount={feedbackAmount}
feedbackSource={feedbackSource}
isSubmitting={isSubmitting}
isVoteFeedbackActive={isVoteFeedbackActive}
latestUsedAmount={latestUsedAmount}
onOpenCustom={handleToggleCustom}
onVoteAmount={onVoteAmount}
quickAmounts={quickAmounts}
Comment thread
ragnep marked this conversation as resolved.
/>
</div>
)}

<div
aria-hidden={hasQuickAmounts ? !isCustomOpen : undefined}
className={clsx(
"tw-absolute tw-inset-0 tw-h-full tw-w-full tw-transform-gpu tw-transition-all tw-duration-300 tw-ease-out motion-reduce:tw-transform-none motion-reduce:tw-transition-none",
!hasQuickAmounts || isCustomOpen ? "tw-z-10" : "tw-z-0",
!hasQuickAmounts || isCustomOpen
? "tw-scale-100 tw-opacity-100"
: "tw-pointer-events-none tw-scale-[0.97] tw-opacity-0"
)}
>
<MemesQuickVoteCustomAmountRow
customAmountLabel={customAmountLabel}
customInputRef={customInputRef}
customValue={customValue}
feedbackSource={feedbackSource}
hasQuickAmounts={hasQuickAmounts}
isCustomRowVisible={isCustomRowVisible}
isSubmitting={isSubmitting}
isVoteFeedbackActive={isVoteFeedbackActive}
onCustomChange={onCustomChange}
onCustomSubmit={onCustomSubmit}
onToggleCustom={handleToggleCustom}
votingLabel={votingLabel}
/>
</div>
</div>
</div>

<button
type="button"
onClick={onSkip}
disabled={isSubmitting}
className="tw-inline-flex tw-h-11 tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-border-iron-900 tw-bg-iron-950 tw-px-5 tw-text-sm tw-font-bold tw-text-iron-200 tw-shadow-[0_10px_24px_rgba(0,0,0,0.18)] tw-transition-all tw-duration-200 disabled:tw-cursor-not-allowed disabled:tw-opacity-60 desktop-hover:hover:tw-border-iron-900 desktop-hover:hover:tw-bg-iron-900 sm:tw-h-12"
>
Skip
</button>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client";

import { formatNumberWithCommas } from "@/helpers/Helpers";
import { CheckIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";

type VoteFeedbackSource = "custom-submit" | "quick-amount";

interface MemesQuickVoteAmountButtonProps {
readonly amount: number;
readonly feedbackAmount: number | null;
readonly feedbackSource: VoteFeedbackSource | null;
readonly isLatestUsed: boolean;
readonly isSubmitting: boolean;
readonly isVoteFeedbackActive: boolean;
readonly onVoteAmount: (amount: number) => void;
}

export default function MemesQuickVoteAmountButton({
amount,
feedbackAmount,
feedbackSource,
isLatestUsed,
isSubmitting,
isVoteFeedbackActive,
onVoteAmount,
}: MemesQuickVoteAmountButtonProps) {
const isQuickVoteFeedbackTarget =
isVoteFeedbackActive &&
feedbackSource === "quick-amount" &&
feedbackAmount === amount;
let buttonToneClassName =
"tw-border-white/5 tw-bg-white/[0.03] tw-text-[14px] tw-font-bold tw-text-zinc-300 tw-shadow-sm tw-transition-colors desktop-hover:hover:tw-bg-white/[0.06] desktop-hover:hover:tw-text-white";

if (isQuickVoteFeedbackTarget) {
buttonToneClassName =
"tw-border-emerald-500/35 tw-bg-emerald-500/15 tw-text-emerald-100 tw-shadow-[0_0_20px_rgba(16,185,129,0.15)]";
} else if (isLatestUsed) {
buttonToneClassName =
"tw-border-blue-500/30 tw-bg-blue-500/15 tw-text-primary-300 tw-shadow-sm tw-transition-all desktop-hover:hover:tw-bg-blue-500/25 desktop-hover:hover:tw-text-primary-200";
}

return (
<button
type="button"
onClick={() => {
onVoteAmount(amount);
}}
disabled={isSubmitting}
className={clsx(
"tw-relative tw-inline-flex tw-h-full tw-min-w-14 tw-flex-none tw-items-center tw-justify-center tw-rounded-xl tw-border tw-border-solid tw-text-center active:tw-scale-95 disabled:tw-cursor-not-allowed disabled:tw-opacity-60 sm:tw-min-w-[4.5rem] sm:tw-flex-1 sm:tw-basis-0 lg:tw-min-w-14 lg:tw-flex-none",
buttonToneClassName,
isVoteFeedbackActive && !isQuickVoteFeedbackTarget && "tw-opacity-40"
)}
>
{isQuickVoteFeedbackTarget ? (
<span className="tw-inline-flex tw-items-center tw-gap-1.5 tw-font-bold tw-leading-none">
<CheckIcon className="tw-size-4 tw-shrink-0" />
<span>{formatNumberWithCommas(amount)}</span>
</span>
) : (
<span
className={clsx(
"tw-font-bold tw-leading-none",
isLatestUsed && "tw-text-primary-300"
)}
>
{formatNumberWithCommas(amount)}
</span>
)}
</button>
);
}
Loading
Loading