Skip to content

Commit e89dfe9

Browse files
committed
feat: add Today/All Time toggle with cross-fade animation
BalanceCard now supports clicking on the change indicator to toggle between Today and All Time values with a snappy cross-fade animation.
1 parent a08d4a4 commit e89dfe9

File tree

1 file changed

+111
-34
lines changed
  • src/pages/prototypes/Wallet/components/BalanceCard

1 file changed

+111
-34
lines changed

src/pages/prototypes/Wallet/components/BalanceCard/index.js

Lines changed: 111 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useState, useEffect } from "react"
2+
import PropTypes from "prop-types"
23
import NumberFlow, { continuous } from "@number-flow/react"
34
import { Spoiler } from "spoiled"
5+
import * as m from "motion/react-m"
6+
import { AnimatePresence } from "motion/react"
47

58
import Train from "../../../../../components/Train"
69
import Text from "../../../../../components/Text"
@@ -9,9 +12,29 @@ import { DURATION, COMPLEX_EASING } from "../../../../../utils/animations"
912

1013
import * as styles from "./BalanceCard.module.scss"
1114

15+
const CHANGE_DATA = {
16+
today: {
17+
value: "+0.82",
18+
percent: "0.11%",
19+
label: "Today",
20+
arrowDirection: "up",
21+
},
22+
allTime: {
23+
value: "+124.56",
24+
percent: "12.34%",
25+
label: "All Time",
26+
arrowDirection: "up",
27+
},
28+
}
29+
30+
const crossFadeTransition = {
31+
duration: 0.1,
32+
ease: [0.32, 0, 0.67, 0],
33+
}
34+
1235
/**
1336
* BalanceCard - универсальный компонент для отображения баланса
14-
*
37+
*
1538
* @param {string} label - Текст лейбла (например, "Balance" или "TON Wallet Balance")
1639
* @param {string} initialBalance - Начальное значение баланса
1740
* @param {"default" | "overlay"} variant - Визуальный вариант: default (светлый фон) или overlay (для тёмного фона)
@@ -27,6 +50,9 @@ export default function BalanceCard({
2750
}) {
2851
const [balance, setBalance] = useState(initialBalance)
2952
const [hidden, setHidden] = useState(false)
53+
const [isToday, setIsToday] = useState(true)
54+
55+
const changeData = isToday ? CHANGE_DATA.today : CHANGE_DATA.allTime
3056

3157
useEffect(() => {
3258
const updateBalance = () => {
@@ -41,15 +67,14 @@ export default function BalanceCard({
4167
return () => clearInterval(interval)
4268
}, [hidden])
4369

44-
const variantClass = variant === "overlay" ? styles.cardOverlay : styles.cardDefault
70+
const variantClass =
71+
variant === "overlay" ? styles.cardOverlay : styles.cardDefault
4572

4673
return (
4774
<div
48-
className={[
49-
styles.card,
50-
variantClass,
51-
className
52-
].filter(Boolean).join(" ")}
75+
className={[styles.card, variantClass, className]
76+
.filter(Boolean)
77+
.join(" ")}
5378
>
5479
<div className={styles.data}>
5580
<Text
@@ -85,36 +110,88 @@ export default function BalanceCard({
85110
}}
86111
/>
87112
</Spoiler>
88-
<Train divider="space">
89-
<Text
90-
apple={{ variant: "subheadline2", weight: "semibold" }}
91-
style={{ color: "var(--text-confirm-color)" }}
92-
>
93-
+0.82
94-
</Text>
95-
<Text.Badge
96-
apple={{
97-
variant: "subheadline2",
98-
weight: "semibold",
99-
arrow: { direction: "up" },
100-
}}
101-
variant="tinted"
102-
circled
103-
style={{
104-
color: "var(--text-confirm-color)",
105-
}}
106-
>
107-
0.11%
108-
</Text.Badge>
109-
<Text
110-
apple={{ variant: "subheadline2", weight: "semibold" }}
111-
style={{ color: "var(--tg-theme-subtitle-text-color)" }}
112-
>
113-
Today
114-
</Text>
113+
<Train
114+
divider="space"
115+
onClick={() => setIsToday((prev) => !prev)}
116+
style={{ cursor: "pointer" }}
117+
>
118+
<AnimatePresence mode="wait" initial={false}>
119+
<m.span
120+
key={`value-${isToday}`}
121+
initial={{ opacity: 0 }}
122+
animate={{ opacity: 1 }}
123+
exit={{ opacity: 0 }}
124+
transition={crossFadeTransition}
125+
>
126+
<Text
127+
apple={{
128+
variant: "subheadline2",
129+
weight: "semibold",
130+
}}
131+
style={{ color: "var(--text-confirm-color)" }}
132+
>
133+
{changeData.value}
134+
</Text>
135+
</m.span>
136+
</AnimatePresence>
137+
<AnimatePresence mode="wait" initial={false}>
138+
<m.span
139+
key={`percent-${isToday}`}
140+
initial={{ opacity: 0 }}
141+
animate={{ opacity: 1 }}
142+
exit={{ opacity: 0 }}
143+
transition={crossFadeTransition}
144+
>
145+
<Text.Badge
146+
apple={{
147+
variant: "subheadline2",
148+
weight: "semibold",
149+
arrow: {
150+
direction: changeData.arrowDirection,
151+
},
152+
}}
153+
variant="tinted"
154+
circled
155+
style={{
156+
color: "var(--text-confirm-color)",
157+
}}
158+
>
159+
{changeData.percent}
160+
</Text.Badge>
161+
</m.span>
162+
</AnimatePresence>
163+
<AnimatePresence mode="wait" initial={false}>
164+
<m.span
165+
key={`label-${isToday}`}
166+
initial={{ opacity: 0 }}
167+
animate={{ opacity: 1 }}
168+
exit={{ opacity: 0 }}
169+
transition={crossFadeTransition}
170+
>
171+
<Text
172+
apple={{
173+
variant: "subheadline2",
174+
weight: "semibold",
175+
}}
176+
style={{
177+
color: "var(--tg-theme-subtitle-text-color)",
178+
}}
179+
>
180+
{changeData.label}
181+
</Text>
182+
</m.span>
183+
</AnimatePresence>
115184
</Train>
116185
</div>
117186
{actions && <div className={styles.actions}>{actions}</div>}
118187
</div>
119188
)
120189
}
190+
191+
BalanceCard.propTypes = {
192+
label: PropTypes.string.isRequired,
193+
initialBalance: PropTypes.string,
194+
variant: PropTypes.oneOf(["default", "overlay"]),
195+
actions: PropTypes.node,
196+
className: PropTypes.string,
197+
}

0 commit comments

Comments
 (0)