feat: add animated components for enhanced UI/UX

- Introduced DrawLine component for drawing animations.
- Added LazyRevealImage component for lazy loading images with reveal animations.
- Implemented TextDiffuseReveal for animated text reveals with staggered effects.
- Created TopProgressBar for a dynamic loading indicator.
- Added PointCloud mock for testing animations.
- Developed useReducedMotion hook to handle user preferences for reduced motion.
- Centralized animation configurations in config.ts for easier management.
- Defined animation variants in variants.ts for consistent animation effects.
- Established AnimationProvider to supply global animation settings.
- Created tests for AnimationProvider and useReducedMotion hook.
- Added SVG assets for branding and UI elements.
- Documented frontend review plan and hero circuit board animation plan.
This commit is contained in:
Evandro Fonseca Junior committed 2026-03-15 21:06:43 -03:00
1 parent 0ab6930f92
commit 08dd046887
76 files changed
+21682 -1197

No files matched your search

+148
View File
@@ -0,0 +1,148 @@
import { render, screen } from "@testing-library/react";
import { AnimationProvider, useAnimationConfig } from "./AnimationProvider";
// Mock do useReducedMotion
jest.mock("@/app/hooks/useReducedMotion", () => ({
useReducedMotion: jest.fn(),
}));
// Test component that uses the hook
function TestComponent() {
const config = useAnimationConfig();
return (
<div>
<div data-testid="reduced-motion">{String(config.reducedMotion)}</div>
<div data-testid="ease-out">{config.easings.easeOut}</div>
<div data-testid="duration-normal">{config.durations.normal}</div>
<div data-testid="spring-default-stiffness">
{config.easings.spring.default.stiffness}
</div>
</div>
);
}
describe("AnimationProvider", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("provides animation config with reducedMotion false", () => {
const { useReducedMotion } = jest.requireMock(
"@/app/hooks/useReducedMotion",
);
useReducedMotion.mockReturnValue(false);
render(
<AnimationProvider>
<TestComponent />
</AnimationProvider>,
);
expect(screen.getByTestId("reduced-motion")).toHaveTextContent("false");
expect(screen.getByTestId("ease-out")).toHaveTextContent(
"cubic-bezier(0.16, 1, 0.3, 1)",
);
expect(screen.getByTestId("duration-normal")).toHaveTextContent("0.6");
expect(screen.getByTestId("spring-default-stiffness")).toHaveTextContent(
"200",
);
});
it("provides animation config with reducedMotion true", () => {
const { useReducedMotion } = jest.requireMock(
"@/app/hooks/useReducedMotion",
);
useReducedMotion.mockReturnValue(true);
render(
<AnimationProvider>
<TestComponent />
</AnimationProvider>,
);
expect(screen.getByTestId("reduced-motion")).toHaveTextContent("true");
});
it("provides all easing configurations", () => {
const { useReducedMotion } = jest.requireMock(
"@/app/hooks/useReducedMotion",
);
useReducedMotion.mockReturnValue(false);
render(
<AnimationProvider>
<TestComponent />
</AnimationProvider>,
);
const config = screen.getByTestId("ease-out").textContent;
expect(config).toBe("cubic-bezier(0.16, 1, 0.3, 1)");
});
it("provides all duration configurations", () => {
const { useReducedMotion } = jest.requireMock(
"@/app/hooks/useReducedMotion",
);
useReducedMotion.mockReturnValue(false);
render(
<AnimationProvider>
<TestComponent />
</AnimationProvider>,
);
const duration = screen.getByTestId("duration-normal").textContent;
expect(duration).toBe("0.6");
});
it("throws error when useAnimationConfig is used outside provider", () => {
// Suppress console.error for this test
const consoleError = jest
.spyOn(console, "error")
.mockImplementation(() => {});
expect(() => {
render(<TestComponent />);
}).toThrow("useAnimationConfig must be used within AnimationProvider");
consoleError.mockRestore();
});
});
describe("useAnimationConfig", () => {
it("returns config object with correct structure", () => {
const { useReducedMotion } = jest.requireMock(
"@/app/hooks/useReducedMotion",
);
useReducedMotion.mockReturnValue(false);
function ConfigStructureTest() {
const config = useAnimationConfig();
return (
<div>
<div data-testid="has-reduced-motion">
{String("reducedMotion" in config)}
</div>
<div data-testid="has-easings">{String("easings" in config)}</div>
<div data-testid="has-durations">{String("durations" in config)}</div>
<div data-testid="has-spring">
{String("spring" in config.easings)}
</div>
</div>
);
}
render(
<AnimationProvider>
<ConfigStructureTest />
</AnimationProvider>,
);
expect(screen.getByTestId("has-reduced-motion")).toHaveTextContent("true");
expect(screen.getByTestId("has-easings")).toHaveTextContent("true");
expect(screen.getByTestId("has-durations")).toHaveTextContent("true");
expect(screen.getByTestId("has-spring")).toHaveTextContent("true");
});
});
+93
View File
@@ -0,0 +1,93 @@
"use client";
import { createContext, useContext, type ReactNode } from "react";
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
import { animationConfig } from "@/app/lib/animations/config";
/**
* Animation Configuration Context
* Provides global animation settings including reduced motion detection
*/
interface AnimationConfig {
reducedMotion: boolean;
easings: typeof animationConfig.easings;
durations: typeof animationConfig.durations;
}
const AnimationContext = createContext<AnimationConfig | undefined>(undefined);
interface AnimationProviderProps {
children: ReactNode;
}
/**
* AnimationProvider
*
* Provider component that supplies global animation configuration through React context.
* Automatically detects user's reduced motion preference and provides centralized
* animation settings (easings, durations) to all child components.
*
* @example
* ```tsx
* // In app/layout.tsx
* <AnimationProvider>
* <YourApp />
* </AnimationProvider>
* ```
*
* @see useAnimationConfig - Hook to consume this context
*/
export function AnimationProvider({ children }: AnimationProviderProps) {
const reducedMotion = useReducedMotion();
const config: AnimationConfig = {
reducedMotion,
easings: animationConfig.easings,
durations: animationConfig.durations,
};
return (
<AnimationContext.Provider value={config}>
{children}
</AnimationContext.Provider>
);
}
/**
* useAnimationConfig
*
* Hook to access global animation configuration from AnimationProvider context.
* Provides reduced motion state, easing curves, and duration presets.
*
* @returns {AnimationConfig} Animation configuration object
* @throws {Error} If used outside of AnimationProvider
*
* @example
* ```tsx
* function MyComponent() {
* const { reducedMotion, easings, durations } = useAnimationConfig();
*
* return (
* <motion.div
* animate={{
* opacity: 1,
* y: reducedMotion ? 0 : 20
* }}
* transition={{
* duration: durations.normal,
* ease: easings.easeOut
* }}
* />
* );
* }
* ```
*/
export function useAnimationConfig(): AnimationConfig {
const context = useContext(AnimationContext);
if (context === undefined) {
throw new Error("useAnimationConfig must be used within AnimationProvider");
}
return context;
}