mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-05-08 17:48:55 -04:00
Add user-controlled animation preferences and reduce motion support
- Persist prefersReducedMotion and animationSpeedMultiplier in user profile - Provide UI controls to toggle reduce motion and adjust animation speed globally - Centralize animation preferences via provider and useAnimationPreferences hook - Apply preferences to charts’ animations (duration, enabled/disabled) - Inline script in layout to apply preferences early and avoid FOUC - Update CSS to respect user preference with reduced motion overrides and variable animation speeds
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";
|
||||
import { useAnimationPreferences } from "~/components/providers/animation-preferences-provider";
|
||||
import { getEffectiveInvoiceStatus } from "~/lib/invoice-status";
|
||||
import type { StoredInvoiceStatus } from "~/types/invoice";
|
||||
|
||||
@@ -51,6 +52,12 @@ export function InvoiceStatusChart({ invoices }: InvoiceStatusChartProps) {
|
||||
paid: "hsl(142, 76%, 36%)", // green
|
||||
overdue: "hsl(0, 84%, 60%)", // red
|
||||
};
|
||||
// Animation / motion preferences
|
||||
const { prefersReducedMotion, animationSpeedMultiplier } =
|
||||
useAnimationPreferences();
|
||||
const pieAnimationDuration = Math.round(
|
||||
600 / (animationSpeedMultiplier || 1),
|
||||
);
|
||||
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
@@ -113,6 +120,9 @@ export function InvoiceStatusChart({ invoices }: InvoiceStatusChartProps) {
|
||||
outerRadius={80}
|
||||
stroke="none"
|
||||
dataKey="count"
|
||||
isAnimationActive={!prefersReducedMotion}
|
||||
animationDuration={pieAnimationDuration}
|
||||
animationEasing="ease-out"
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "recharts";
|
||||
import { getEffectiveInvoiceStatus } from "~/lib/invoice-status";
|
||||
import type { StoredInvoiceStatus } from "~/types/invoice";
|
||||
import { useAnimationPreferences } from "~/components/providers/animation-preferences-provider";
|
||||
|
||||
interface Invoice {
|
||||
id: string;
|
||||
@@ -87,6 +88,13 @@ export function MonthlyMetricsChart({ invoices }: MonthlyMetricsChartProps) {
|
||||
}),
|
||||
}));
|
||||
|
||||
// Animation / motion preferences
|
||||
const { prefersReducedMotion, animationSpeedMultiplier } =
|
||||
useAnimationPreferences();
|
||||
const barAnimationDuration = Math.round(
|
||||
500 / (animationSpeedMultiplier || 1),
|
||||
);
|
||||
|
||||
const CustomTooltip = ({
|
||||
active,
|
||||
payload,
|
||||
@@ -167,24 +175,36 @@ export function MonthlyMetricsChart({ invoices }: MonthlyMetricsChartProps) {
|
||||
stackId="a"
|
||||
fill="hsl(0, 0%, 60%)"
|
||||
radius={[0, 0, 0, 0]}
|
||||
isAnimationActive={!prefersReducedMotion}
|
||||
animationDuration={barAnimationDuration}
|
||||
animationEasing="ease-out"
|
||||
/>
|
||||
<Bar
|
||||
dataKey="paidInvoices"
|
||||
stackId="a"
|
||||
fill="var(--chart-2)"
|
||||
radius={[0, 0, 0, 0]}
|
||||
isAnimationActive={!prefersReducedMotion}
|
||||
animationDuration={barAnimationDuration}
|
||||
animationEasing="ease-out"
|
||||
/>
|
||||
<Bar
|
||||
dataKey="pendingInvoices"
|
||||
stackId="a"
|
||||
fill="var(--chart-1)"
|
||||
radius={[0, 0, 0, 0]}
|
||||
isAnimationActive={!prefersReducedMotion}
|
||||
animationDuration={barAnimationDuration}
|
||||
animationEasing="ease-out"
|
||||
/>
|
||||
<Bar
|
||||
dataKey="overdueInvoices"
|
||||
stackId="a"
|
||||
fill="var(--chart-3)"
|
||||
radius={[2, 2, 0, 0]}
|
||||
isAnimationActive={!prefersReducedMotion}
|
||||
animationDuration={barAnimationDuration}
|
||||
animationEasing="ease-out"
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "recharts";
|
||||
import { getEffectiveInvoiceStatus } from "~/lib/invoice-status";
|
||||
import type { StoredInvoiceStatus } from "~/types/invoice";
|
||||
import { useAnimationPreferences } from "~/components/providers/animation-preferences-provider";
|
||||
|
||||
interface Invoice {
|
||||
id: string;
|
||||
@@ -99,6 +100,8 @@ export function RevenueChart({ invoices }: RevenueChartProps) {
|
||||
return null;
|
||||
};
|
||||
|
||||
const { prefersReducedMotion, animationSpeedMultiplier } =
|
||||
useAnimationPreferences();
|
||||
if (chartData.length === 0) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
@@ -147,6 +150,11 @@ export function RevenueChart({ invoices }: RevenueChartProps) {
|
||||
stroke="hsl(0, 0%, 60%)"
|
||||
strokeWidth={2}
|
||||
fill="url(#revenueGradient)"
|
||||
isAnimationActive={!prefersReducedMotion}
|
||||
animationDuration={Math.round(
|
||||
600 / (animationSpeedMultiplier ?? 1),
|
||||
)}
|
||||
animationEasing="ease-out"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
Reference in New Issue
Block a user