Skip to content

Commit

Permalink
Animate last month
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Dec 20, 2023
1 parent 484aaf9 commit 36319dc
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 9 deletions.
2 changes: 0 additions & 2 deletions apps/dashboard/src/actions/share-file-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,5 @@ export const shareFileAction = action(shareFileSchema, async (value) => {
},
});

console.log(response);

return response?.data?.signedUrl;
});
15 changes: 9 additions & 6 deletions apps/dashboard/src/components/charts/chart.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Counter } from "@/components/counter";
import { formatAmount } from "@/utils/format";
import { getMetrics } from "@midday/supabase/cached-queries";
import { cn } from "@midday/ui/utils";
Expand All @@ -11,16 +12,18 @@ export async function Chart({ value, defaultValue, disabled }) {
? chartData
: await getMetrics({ ...defaultValue, ...value, type });

const lastPeriodAmount = data.result[data.result.length - 1]?.current?.value;

return (
<div className="relative mt-28">
<div className="absolute -top-[110px] space-y-2">
<h1 className={cn("text-3xl", disabled && "skeleton-box")}>
{formatAmount({
amount: data.summary.currentTotal || 0,
currency: data.summary.currency,
maximumFractionDigits: 0,
minimumFractionDigits: 0,
})}
<Counter
value={data.summary.currentTotal}
currency={data.summary.currency}
lastPeriodAmount={lastPeriodAmount}
locale="sv-SE"
/>
</h1>
<p className={cn("text-sm text-[#606060]", disabled && "skeleton-box")}>
vs{" "}
Expand Down
104 changes: 104 additions & 0 deletions apps/dashboard/src/components/counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use client";

import { useEffect, useRef, useState } from "react";

const CONFIG = {
min: 0,
max: 20000,
};

const Character = ({ className, key, value }) => {
return (
<span data-value={value} className={`character ${className || ""}`}>
<span className="character__track" style={{ "--v": value }}>
<span>9</span>
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((val, index) => {
return <span key={`${key}--${index}`}>{val}</span>;
})}
<span>0</span>
</span>
</span>
);
};

const InnerCounter = ({ currency, pad, value, locale }) => {
const padCount = pad
? CONFIG.max.toFixed(2).toString().length - value.toString().length
: 0;

const paddedValue = value
.toString()
.padStart(value.toString().length + padCount, "1");

let i = 0;
const renderValue = new Intl.NumberFormat(locale, {
style: "currency",
currency,
})
.format(paddedValue)
.split("")
.map((character, index) => {
if (!Number.isNaN(parseInt(character, 10)) && i < padCount) {
i++;
return "0";
}
return character;
})
.join("");

return (
<div className="counter">
<fieldset>
<h2>
<span className="sr-only">{renderValue}</span>
<span aria-hidden="true" className="characters">
{renderValue.split("").map((character, index) => {
if (Number.isNaN(parseInt(character, 10)))
return (
<span
key={index.toString()}
className="character character--symbol"
>
{character}
</span>
);
return (
<Character
key={index.toString()}
value={character}
className={
index > renderValue.split("").length - 3 ? "fraction" : ""
}
/>
);
})}
</span>
</h2>
</fieldset>
</div>
);
};

export function Counter({
value: initalValue,
currency,
locale,
lastPeriodAmount = 0,
}) {
const hasRunned = useRef(false);
const [value, setValue] = useState(initalValue - lastPeriodAmount);

useEffect(() => {
if (!hasRunned.current) {
setValue((prev) => prev + lastPeriodAmount);
hasRunned.current = true;
}
}, [lastPeriodAmount, hasRunned]);

return (
<>
<InnerCounter pad value={value} currency={currency} locale={locale} />
<InnerCounter pad value={value} currency={currency} locale={locale} />
</>
);
}
231 changes: 230 additions & 1 deletion apps/dashboard/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,233 @@ body {
25% { transform: translateX(0.5rem); }
75% { transform: translateX(-0.5rem); }
100% { transform: translateX(0rem); }
}
}

:root {
--transition: 1;
--line: hsl(0 0% 95% / 0.25);
--basic: ease;
--back:
linear( 0, -0.0077 5.2%, -0.0452 16.98%, -0.0493 22.35%, -0.0418 25.57%,
-0.0258 28.57%, -0.0007 31.42%, 0.0335 34.15%, 0.1242 39.03%, 0.2505 43.65%,
0.3844 47.35%, 0.656 53.68%, 0.81 58.37%, 0.9282 63.52%, 0.9719 66.23%,
1.0055 69.04%, 1.0255 71.4%, 1.0396 73.87%, 1.0477 76.48%, 1.05 79.27%,
1.0419 84.36%, 1.0059 95.49%, 1 );
--expo:
linear( 0, 0.0053 17.18%, 0.0195 26.59%, 0.0326 30.31%, 0.0506 33.48%,
0.0744 36.25%, 0.1046 38.71%, 0.1798 42.62%, 0.2846 45.93%, 0.3991 48.37%,
0.6358 52.29%, 0.765 55.45%, 0.8622 59.3%, 0.8986 61.51%, 0.9279 63.97%,
0.9481 66.34%, 0.9641 69.01%, 0.9856 75.57%, 0.9957 84.37%, 1 );
--sine:
linear( 0, 0.007 5.35%, 0.0282 10.75%, 0.0638 16.26%, 0.1144 21.96%,
0.1833 28.16%, 0.2717 34.9%, 0.6868 62.19%, 0.775 68.54%, 0.8457 74.3%,
0.9141 81.07%, 0.9621 87.52%, 0.9905 93.8%, 1 );
--power:
linear( 0, 0.0012 14.95%, 0.0089 22.36%, 0.0297 28.43%, 0.0668 33.43%,
0.0979 36.08%, 0.1363 38.55%, 0.2373 43.07%, 0.3675 47.01%, 0.5984 52.15%,
0.7121 55.23%, 0.8192 59.21%, 0.898 63.62%, 0.9297 66.23%, 0.9546 69.06%,
0.9733 72.17%, 0.9864 75.67%, 0.9982 83.73%, 1 );
--circ:
linear( -0, 0.0033 5.75%, 0.0132 11.43%, 0.0296 16.95%, 0.0522 22.25%,
0.0808 27.25%, 0.1149 31.89%, 0.1542 36.11%, 0.1981 39.85%, 0.2779 44.79%,
0.3654 48.15%, 0.4422 49.66%, 0.5807 50.66%, 0.6769 53.24%, 0.7253 55.37%,
0.7714 58.01%, 0.8142 61.11%, 0.8536 64.65%, 0.9158 72.23%, 0.9619 80.87%,
0.9904 90.25%, 1 );
--bounce:
linear( 0, 0.0039, 0.0157, 0.0352, 0.0625 9.09%,
0.1407, 0.25, 0.3908, 0.5625, 0.7654,
1, 0.8907, 0.8125 45.45%, 0.7852, 0.7657,
0.7539, 0.75, 0.7539, 0.7657, 0.7852,
0.8125 63.64%, 0.8905, 1 72.73%, 0.9727, 0.9532,
0.9414, 0.9375, 0.9414, 0.9531, 0.9726,
1, 0.9883, 0.9844, 0.9883, 1 );
--elastic:
linear( 0, 0.0009 8.51%, -0.0047 19.22%, 0.0016 22.39%, 0.023 27.81%,
0.0237 30.08%, 0.0144 31.81%, -0.0051 33.48%, -0.1116 39.25%, -0.1181 40.59%,
-0.1058 41.79%, -0.0455, 0.0701 45.34%, 0.9702 55.19%, 1.0696 56.97%,
1.0987 57.88%, 1.1146 58.82%, 1.1181 59.83%, 1.1092 60.95%, 1.0057 66.48%,
0.986 68.14%, 0.9765 69.84%, 0.9769 72.16%, 0.9984 77.61%, 1.0047 80.79%,
0.9991 91.48%, 1 );
--ease: var(--back);
}

.counter h2 {
transform-style: flat;
}

.counter {
display: grid;
place-items: center;
}

.counter fieldset .sr-only {
position: absolute;
font-size: var(--font-size);
line-height: var(--line-height);
z-index: 2;
font-variant: tabular-nums;
color: transparent;
}

.counter fieldset {
--mask-size: 0.25;
/* --font-size: clamp(20px, 4vw + 1rem, 8rem); */
--font-size: 35px;
--line-height: calc(var(--font-size) * 1.5);
padding: 0;
margin: 0;
border: 0;
}

.counter legend {
color: hsl(20 80% 50%);
border: 0;
font-size: calc(var(--font-size) * 0.25);
}

.character {
display: grid;
height: 1lh;
line-height: var(--line-height);
font-variant: tabular-nums;
font-size: var(--font-size);
overflow: hidden;
mask: linear-gradient(transparent, white calc(1lh * var(--mask-size)) calc(100% - (1lh * var(--mask-size))), transparent);
transform-style: flat;
}

.character__track span {
height: 1lh;
font-weight: 120;
transform-style: flat;
}

.character,
.character__track span {
/* background: linear-gradient(hsl(0 0% 98%) 50%, hsl(0 0% 45%)); */
background-color: white;
background-attachment: fixed;
background-clip: text;
color: transparent;
transform-style: flat;
}

.character {
font-weight: 20;
}

.character__track {
display: grid;
translate: 0 calc((var(--v) + 1) * (var(--line-height) * -1));
transition: translate calc(var(--transition) * 1s) var(--ease);
}

/* .character:first-of-type {
margin-right: 0.2ch;
opacity: 0.75;
font-size: calc(var(--font-size) * 0.8);
} */

.character:first-of-type {
/* margin-right: 0.2ch; */
/* opacity: 0.75; */
/* font-size: calc(var(--font-size) * 0.8); */
}

.characters {
display: flex;
gap: 2px;
transform-style: flat;
}

.fraction {
font-size: calc(var(--font-size) * 0.75);
opacity: 0.75;
font-weight: 40;
height: var(--line-height);
}

.fraction .character__track span {
display: flex;
flex-direction: column;
align-items: end;
padding: calc((var(--line-height) - var(--font-size)) * 0.2) 0;
}

.counter:last-of-type {
position: absolute;
opacity: 0;
pointer-events: none;
}

.counter:last-of-type [data-value] {
position: relative;
}

.counter:last-of-type [data-value]::after {
content: attr(data-value);
position: absolute;
bottom: 0%;
left: 0%;
font-family: "Geist Sans", sans-serif;
font-weight: 120;

background: linear-gradient(hsl(0 0% 98%) 50%, hsl(0 0% 45%));
background-attachment: fixed;
background-clip: text;
color: transparent;
}

.counter:last-of-type .fraction[data-value]::after {
display: flex;
flex-direction: column;
align-items: end;
padding: calc((var(--line-height) - var(--font-size)) * 0.2) 0;
height: var(--line-height);
}

.dg.ac {
z-index: 9999 !important;
transform: translate3d(0, 0, 100vmin);
}

.counter:first-of-type {
transform: translate3d(0, 0, calc(var(--depth) * 1));
}

.counter:last-of-type {
transform: translate3d(0, 0, calc(var(--depth) * -1));
}

.counter {
transition: transform 0.5s 2s;
}

.counter:last-of-type {
transition: transform 0.5s 2s ease, opacity 0.5s 2s steps(1, end), background 0.5s 1s, border-color 0.5s 1s;
}

.counter:last-of-type .character {
mask: unset;
overflow: visible;
}

.counter:last-of-type .character__track span {
opacity: 0;
}

.counter:last-of-type .character--symbol,
.counter:last-of-type .character__track span {
transition: opacity 0.5s;
}
.counter:last-of-type [data-value]::after {
transition: opacity 0.5s;
}
.counter:last-of-type legend {
transition: color 0.5s 1s;
}





0 comments on commit 36319dc

Please sign in to comment.