npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

shade-shift

v0.0.2

Published

Advanced theme transition library for Next.js applications

Downloads

4

Readme

ShadeShift: Advanced Theme Transitions for Next.js

npm version Build Status Coverage Status License: MIT TypeScript Next.js PRs Welcome

ShadeShift is a powerful, flexible, and performant theme transition library designed specifically for Next.js applications. It provides seamless, animated transitions between themes, enhancing user experience and adding a layer of polish to your web applications.

🌟 Key Features

  • 🎨 Smooth, customizable theme transitions
  • 🚀 Optimized for performance with requestAnimationFrame
  • 🔄 Seamless integration with Next.js and next-themes
  • 🧩 Modular architecture for easy extensibility
  • 📱 Responsive design support
  • 🔧 Fine-grained control over transition parameters
  • 🔌 Plugin system for custom transition effects
  • 📊 Built-in analytics for transition performance monitoring
  • 💡 TypeScript support for enhanced developer experience
  • 🛠️ Easy to use API with hooks and components

Table of Contents

🚀 Quick Start

Get started with ShadeShift in your Next.js project by following these steps:

  1. Installation

    Install ShadeShift and its peer dependency:

    npm install shade-shift next-themes
    # or
    yarn add shade-shift next-themes
    # or
    pnpm add shade-shift next-themes
  2. Setup ShadeShiftProvider

    You have two options to set up the ShadeShiftProvider:

    // context/theme-provider.tsx
    "use client";
    import * as React from "react";
    import { ThemeProvider as NextThemesProvider } from "next-themes";
    import { type ThemeProviderProps } from "next-themes/dist/types";
    import { ShadeShiftProvider } from "shade-shift";
    
    export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
      return (
        <ShadeShiftProvider>
          <NextThemesProvider {...props}>{children}</NextThemesProvider>
        </ShadeShiftProvider>
      );
    }

    Then, use this ThemeProvider in your _app.tsx or layout.tsx:

    // pages/_app.tsx or app/layout.tsx
    import { ThemeProvider } from "../context/theme-provider";
    
    export default function App({ Component, pageProps }) {
      return (
        <ThemeProvider attribute="class" defaultTheme="dark">
          <Component {...pageProps} />
        </ThemeProvider>
      );
    }
    // pages/_app.tsx
    import { ShadeShiftProvider } from "shade-shift";
    import { ThemeProvider } from "next-themes";
    
    function MyApp({ Component, pageProps }) {
      return (
        <ShadeShiftProvider>
          <ThemeProvider attribute="class" defaultTheme="dark">
            <Component {...pageProps} />
          </ThemeProvider>
        </ShadeShiftProvider>
      );
    }
    
    export default MyApp;
  3. Implement Theme Toggle

    You can now use ShadeShift's hooks to implement theme toggling. Here are two approaches:

    import { ThemeToggle } from "shade-shift/components";
    
    function MyComponent() {
      return (
        <div>
          <h1>My App</h1>
          <ThemeToggle />
        </div>
      );
    }
    "use client";
    import * as React from "react";
    import { AnimatePresence, motion } from "framer-motion";
    import { DesktopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons";
    import {
      Tooltip,
      TooltipArrow,
      TooltipContent,
      TooltipProvider,
      TooltipTrigger,
    } from "@/components/ui/tooltip";
    import { useEffect, useState } from "react";
    import { Button } from "@/components/ui/button";
    import { useTheme } from "next-themes";
    import { shade_shift } from "shade-shift";
    
    export function ThemeToggle() {
      const { theme, setTheme } = useTheme();
      const [currentTheme, setCurrentTheme] = useState<string | undefined>(
        undefined
      );
      const { transition_theme } = shade_shift.use_theme_transition();
    
      useEffect(() => {
        setCurrentTheme(theme);
      }, [theme]);
    
      const handleToggle = () => {
        setCurrentTheme((prevTheme) => {
          let newTheme;
          if (prevTheme === "light") {
            newTheme = "dark";
          } else if (prevTheme === "dark") {
            newTheme = "system";
          } else {
            newTheme = "light";
          }
          transition_theme(newTheme, {
            type: "vortex",
            duration: 1000,
            intensity: 5,
          });
          return newTheme;
        });
      };
    
      if (currentTheme === undefined) {
        return null;
      }
    
      return (
        <TooltipProvider>
          <Tooltip>
            <TooltipTrigger asChild>
              <Button
                variant="ringHoverOutline"
                size="icon"
                onClick={handleToggle}
                className="relative flex items-center justify-center"
              >
                <AnimatePresence initial={false} mode="wait">
                  {currentTheme === "light" && (
                    <motion.div
                      key="light"
                      initial={{ opacity: 0, rotate: -90, scale: 0 }}
                      animate={{ opacity: 1, rotate: 0, scale: 1 }}
                      exit={{ opacity: 0, rotate: 90, scale: 0 }}
                      transition={{ duration: 0.15 }}
                      className="absolute"
                    >
                      <SunIcon className="h-[1.2rem] w-[1.2rem]" />
                    </motion.div>
                  )}
                  {currentTheme === "dark" && (
                    <motion.div
                      key="dark"
                      initial={{ opacity: 0, rotate: -90, scale: 0 }}
                      animate={{ opacity: 1, rotate: 0, scale: 1 }}
                      exit={{ opacity: 0, rotate: 90, scale: 0 }}
                      transition={{ duration: 0.15 }}
                      className="absolute"
                    >
                      <MoonIcon className="h-[1.2rem] w-[1.2rem]" />
                    </motion.div>
                  )}
                  {currentTheme === "system" && (
                    <motion.div
                      key="system"
                      initial={{ opacity: 0, rotate: -90, scale: 0 }}
                      animate={{ opacity: 1, rotate: 0, scale: 1 }}
                      exit={{ opacity: 0, rotate: 90, scale: 0 }}
                      transition={{ duration: 0.15 }}
                      className="absolute"
                    >
                      <DesktopIcon className="h-[1.2rem] w-[1.2rem]" />
                    </motion.div>
                  )}
                </AnimatePresence>
                <span className="sr-only">Toggle theme</span>
              </Button>
            </TooltipTrigger>
            <TooltipContent sideOffset={10}>
              {currentTheme === "light" && <p>Light Mode</p>}
              {currentTheme === "dark" && <p>Dark Mode</p>}
              {currentTheme === "system" && <p>System Mode</p>}
              <TooltipArrow className="fill-primary" />
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      );
    }
  4. Optional: Set up Route-based Theme Switching

    If you want to change themes based on routes, you can use the useRouteThemeSync hook:

    import { shadeShift } from "shade-shift";
    
    function MyApp({ Component, pageProps }) {
      shadeShift.useRouteThemeSync({
        "/": { theme: "light", transition: { type: "fade", duration: 500 } },
        "/dark-mode": {
          theme: "dark",
          transition: { type: "vortex", duration: 1000 },
        },
      });
    
      return <Component {...pageProps} />;
    }

That's it! You now have ShadeShift set up in your Next.js project with smooth theme transitions.

🛠️ Installation

To install ShadeShift, run one of the following commands in your project directory:

npm install shade-shift next-themes
# or
yarn add shade-shift next-themes
# or
pnpm add shade-shift next-themes

🔧 Usage

Setting up ShadeShiftProvider

The ShadeShiftProvider component is the core of ShadeShift. It manages the theme transition state and provides the necessary context for all ShadeShift hooks and components.

import { ShadeShiftProvider } from "shade-shift";
import { ThemeProvider } from "next-themes";

function MyApp({ Component, pageProps }) {
  return (
    <ShadeShiftProvider>
      <ThemeProvider attribute="class" defaultTheme="dark">
        <Component {...pageProps} />
      </ThemeProvider>
    </ShadeShiftProvider>
  );
}

export default MyApp;

Implementing Theme Toggle

You can implement theme toggling using either the pre-built ThemeToggle component or by creating a custom component using ShadeShift hooks.

import { ThemeToggle } from "shade-shift/components";

function MyComponent() {
  return (
    <div>
      <h1>My App</h1>
      <ThemeToggle />
    </div>
  );
}
import { shadeShift } from "shade-shift";
import { useTheme } from "next-themes";

function CustomThemeToggle() {
  const { theme, setTheme } = useTheme();
  const { transitionTheme } = shadeShift.useThemeTransition();

  const toggleTheme = () => {
    const newTheme = theme === "light" ? "dark" : "light";
    transitionTheme(newTheme, {
      type: "vortex",
      duration: 1000,
      intensity: 5,
    });
  };

  return <button onClick={toggleTheme}>Toggle Theme</button>;
}

Route-based Theme Switching

ShadeShift allows you to change themes based on routes using the useRouteThemeSync hook:

import { shadeShift } from "shade-shift";

function MyApp({ Component, pageProps }) {
  shadeShift.useRouteThemeSync({
    "/": { theme: "light", transition: { type: "fade", duration: 500 } },
    "/dark-mode": {
      theme: "dark",
      transition: { type: "vortex", duration: 1000 },
    },
  });

  return <Component {...pageProps} />;
}

🧠 Core Concepts

ShadeShift is built around several core concepts:

  1. Transition Engine: Manages the queue and execution of theme transitions.
  2. Transition Effects: Implements various visual effects for theme transitions.
  3. Theme Provider: Integrates with next-themes for seamless theme management.
  4. Route Sync: Allows for automatic theme changes based on routes.

📚 API Reference

ShadeShiftProvider

The main provider component that wraps your application.

<ShadeShiftProvider>{/* Your app components */}</ShadeShiftProvider>

useThemeTransition

A hook that provides theme transition functionality.

const { theme, transitionTheme, transitionState } =
  shadeShift.useThemeTransition();
  • theme: The current theme
  • transitionTheme(newTheme: string, config: TransitionConfig): Function to trigger a theme transition
  • transitionState: Current state of the transition

useRouteThemeSync

A hook that synchronizes themes with routes.

const routeThemeMap = {
  "/": { theme: "light", transition: { type: "fade", duration: 500 } },
  "/dark-mode": {
    theme: "dark",
    transition: { type: "vortex", duration: 1000 },
  },
};

shadeShift.useRouteThemeSync(routeThemeMap);

TransitionConfig

Configuration object for transitions.

interface TransitionConfig {
  type: "splitScreen" | "diagonalWipe" | "morphology" | "pixelate" | "vortex";
  duration: number;
  easing: string;
  direction?:
    | "horizontal"
    | "vertical"
    | "topLeft"
    | "topRight"
    | "bottomLeft"
    | "bottomRight";
  intensity?: number;
}

🔬 Advanced Usage

Custom Transition Effects

You can create custom transition effects by implementing the TransitionFunction type:

import { TransitionFunction } from "shade-shift";

const myCustomTransition: TransitionFunction = async (
  element,
  fromTheme,
  toTheme,
  config,
  onProgress
) => {
  const { duration } = config;
  const start = performance.now();

  const animate = (now: number) => {
    const progress = Math.min((now - start) / duration, 1);
    onProgress(progress);

    // Implement your custom animation logic here
    element.style.backgroundColor = progress < 0.5 ? fromTheme : toTheme;

    if (progress < 1) {
      requestAnimationFrame(animate);
    }
  };

  requestAnimationFrame(animate);

  return new Promise<void>((resolve) => {
    setTimeout(resolve, duration);
  });
};

// Usage
transitionTheme("dark", {
  type: "custom",
  duration: 1000,
  customTransition: myCustomTransition,
});

Transition Chaining

Chain multiple transitions for complex effects:

const complexTransition = async () => {
  await transitionTheme("intermediateTheme", { type: "fade", duration: 500 });
  await transitionTheme("finalTheme", { type: "vortex", duration: 1000 });
};

// Usage
<button onClick={complexTransition}>Complex Transition</button>;

Dynamic Transition Parameters

Adjust transition parameters based on device capabilities or user preferences:

const adaptiveTransition = () => {
  const { performanceScore } = getDeviceCapabilities();
  const duration = performanceScore > 80 ? 1000 : 500;
  transitionTheme("newTheme", { type: "morphology", duration });
};

// Usage
<button onClick={adaptiveTransition}>Adaptive Transition</button>;

⚡ Performance Optimization

ShadeShift is designed with performance in mind, but here are some tips to ensure smooth transitions:

  1. Use the throttle and debounce utilities for event handlers.
  2. Implement progressive enhancement for low-end devices.
  3. Optimize your theme stylesheets to minimize CSSOM operations.
  4. Use the built-in performance monitoring tools to identify bottlenecks.
  5. Consider using the will-change CSS property to optimize animations.
import { shadeShift } from "shade-shift";

// Throttle theme transitions
const throttledTransition = shadeShift.throttle((newTheme, config) => {
  shadeShift.transitionTheme(newTheme, config);
}, 200);

// Debounce theme changes based on user input
const debouncedThemeChange = shadeShift.debounce((newTheme) => {
  shadeShift.transitionTheme(newTheme, { type: "fade", duration: 300 });
}, 300);

// Progressive enhancement
const performTransition = (newTheme, config) => {
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
    // Simplified transition for users who prefer reduced motion
    shadeShift.transitionTheme(newTheme, { type: "fade", duration: 100 });
  } else {
    shadeShift.transitionTheme(newTheme, config);
  }
};

// Optimize CSS animations
const optimizeForAnimation = (element) => {
  element.style.willChange = "transform, opacity";
  // Perform animation
  element.addEventListener("transitionend", () => {
    element.style.willChange = "auto";
  });
};

🎨 Styling and Theming

ShadeShift is designed to work seamlessly with your existing styling solutions. Here are some tips for integrating ShadeShift with popular styling approaches:

CSS Variables

Use CSS variables to define your theme colors and easily switch between them:

:root {
  --background: white;
  --text: black;
}

[data-theme="dark"] {
  --background: black;
  --text: white;
}

body {
  background-color: var(--background);
  color: var(--text);
}

Tailwind CSS

If you're using Tailwind CSS, you can leverage its built-in dark mode support. Here's how to set it up in your globals.css or main CSS file:

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: white;
    --text: black;
  }
  .dark {
    --background: black;
    --text: white;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Then, in your Tailwind config file (tailwind.config.js), ensure you have the dark mode set up:

module.exports = {
  darkMode: "class",
  theme: {
    extend: {
      colors: {
        background: "var(--background)",
        foreground: "var(--text)",
      },
    },
  },
  // ... other configurations
};

Now you can use these variables in your components:

function MyComponent() {
  return (
    <div className="bg-background text-foreground">
      {/* Your component content */}
    </div>
  );
}

Styled Components

For Styled Components users, you can access the current theme in your styles:

import styled from "styled-components";

const StyledComponent = styled.div`
  background-color: ${(props) => props.theme.background};
  color: ${(props) => props.theme.text};
`;

🌐 Browser Compatibility

ShadeShift is compatible with all modern browsers. For older browsers, we provide fallback options that gracefully degrade the transition effects.

| Browser | Minimum Version | | ------- | --------------- | | Chrome | 60 | | Firefox | 55 | | Safari | 11 | | Edge | 79 | | IE | Not supported |

🔍 Troubleshooting

Here are some common issues and their solutions:

  1. Transitions not working

    • Ensure ShadeShiftProvider is at the root of your app.
    • Check if next-themes is properly configured.
  2. Flickering during transitions

    • Check for conflicting CSS animations.
    • Ensure your CSS is properly scoped to avoid overrides.
  3. Performance issues

    • Use the performance optimization tips mentioned above.
    • Consider simplifying complex transitions on low-end devices.
  4. TypeScript errors

    • Make sure you're using the latest version of ShadeShift.
    • Check if your tsconfig.json includes all necessary compiler options.

If you encounter any other issues, please check our GitHub issues page or open a new issue.

🤝 Contributing

We welcome contributions to ShadeShift! Here's how you can help:

  1. Fork the repository and create your branch from main.
  2. If you've added code that should be tested, add tests.
  3. Ensure the test suite passes.
  4. Make sure your code lints.
  5. Issue that pull request!

Please see our Contributing Guide for more details.

📄 License

ShadeShift is MIT licensed. See the LICENSE file for details.

📈 Star History

Star History Chart


Built with ❤️ by BankkRoll