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:
1 parent
0ab6930f92
commit
08dd046887
76 files changed
+21682
-1197
No files matched your search
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user