Toggle list layout

PreviousNext

Toggle list layout, toggling between a list and a grid view.

Docs
systaliko-uiblock

Preview

Loading preview…
registry/ecommerce/toggle-layout/index.tsx
'use client';
import { HTMLMotionProps, LayoutGroup, motion } from 'motion/react';
import { cn } from '@/lib/utils';
import React from 'react';

const layout_config = {
  list: {
    mode: 'list',
    className: 'flex flex-col space-y-4',
    label: 'list view',
  },
  col2: {
    mode: 'col2',
    className: 'md:grid md:grid-cols-2 gap-4',
    label: '2 column view',
  },
  col3: {
    mode: 'col3',
    className: 'md:grid md:grid-cols-3 gap-4',
    label: '3 column view',
  },
  col4: {
    mode: 'col4',
    className: 'md:grid md:grid-cols-4 gap-4',
    label: '4 column view',
  },
};
const animation_variants = {
  container: {
    list: { transition: { staggerChildren: 0.02 } },
    col2: { transition: { staggerChildren: 0.1 } },
    col3: { transition: { staggerChildren: 0.15 } },
    col4: { transition: { staggerChildren: 0.2 } },
    auto: { transition: { staggerChildren: 0.25 } },
  },
  card: {
    hidden: { opacity: 0, y: 20 },
    visible: { opacity: 1, y: 0 },
  },
};
interface ToggleLayoutContextValue {
  modeIndex: number;
  setModeIndex: React.Dispatch<React.SetStateAction<number>>;
}
const ToggleLayoutContext = React.createContext<
  ToggleLayoutContextValue | undefined
>(undefined);
function useToggleLayoutContext() {
  const context = React.useContext(ToggleLayoutContext);
  if (context === undefined) {
    throw new Error(
      'useToggleLayoutContext must be used within a ToggleLayoutProvider',
    );
  }
  return context;
}
export function ToggleLayout({
  children,
  ...props
}: React.ComponentProps<typeof LayoutGroup>) {
  const [modeIndex, setModeIndex] = React.useState<number>(0);

  return (
    <ToggleLayoutContext.Provider value={{ modeIndex, setModeIndex }}>
      <LayoutGroup {...props}>{children}</LayoutGroup>
    </ToggleLayoutContext.Provider>
  );
}
export function ToggleLayoutContainer({
  className,
  ...props
}: HTMLMotionProps<'div'>) {
  const { modeIndex } = useToggleLayoutContext();
  const layout_config_values = [...Object.values(layout_config)];

  const currentConfig = layout_config_values[modeIndex];

  return (
    <motion.div
      layout
      variants={animation_variants.container}
      initial="hidden"
      animate={currentConfig.mode}
      className={cn(currentConfig.className, className)}
      {...props}
    />
  );
}

export function SelectLayoutGroup({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) {
  const { modeIndex, setModeIndex } = useToggleLayoutContext();
  const layout_config_values = [...Object.values(layout_config)];

  return (
    <div
      className={cn(
        'relative flex -space-x-px -space-y-px flex-col items-start md:flex-row justify-start   w-fit',
        className,
      )}
      {...props}
    >
      {layout_config_values.map((config, index) => (
        <div className="relative" id="layout-toggle-button" key={config.mode}>
          <button
            onClick={() => setModeIndex(index)}
            className="appearance-none border  bg-none text-nowrap text-sm px-2.5 font-medium py-2"
          >
            <span className="hover:underline-none">{config.label}</span>
          </button>
          {index === modeIndex && (
            <motion.div
              className="mix-blend-difference z-2 rounded-inherit absolute inset-0 bg-secondary"
              layoutId="layout-toggle-button"
            />
          )}
        </div>
      ))}
    </div>
  );
}

export function ToggleLayoutCell({ ...props }: HTMLMotionProps<'div'>) {
  return (
    <motion.div
      layout
      variants={animation_variants.card}
      initial="hidden"
      animate="visible"
      transition={{ type: 'spring', stiffness: 200, damping: 25 }}
      exit="hidden"
      {...props}
    />
  );
}

Installation

npx shadcn@latest add @systaliko-ui/toggle-layout

Usage

import { ToggleLayout } from "@/components/toggle-layout"
<ToggleLayout />