Toolbar

Usage

import {Bell, Bold, FolderOpen, Heart, Inbox, Italic, MessageCircle, Strikethrough} from "lucide-react";

import {
  Toolbar,
  ToolbarButton,
  ToolbarButtonLink,
  ToolbarLink,
  ToolbarPortal,
  ToolbarSeparator,
  ToolbarToggleGroup,
  ToolbarToggleItem,
} from "@/components/ui/toolbar";

const items = [
  {
    id: 0,
    type: "link",
    label: "Notifications",
    icon: Bell,
  },
  {
    id: 1,
    type: "item",
    label: "Inbox",
    icon: Inbox,
  },
  {
    id: 2,
    type: "separator",
  },
  {
    id: 3,
    type: "item",
    label: "Likes",
    icon: Heart,
  },
  {
    id: 4,
    type: "item",
    label: "Files",
    icon: FolderOpen,
  },
];

const ToolbarComponent = () => {
  return (
    <ToolbarPortal>
      <Toolbar orientation="horizontal" position={"bottom"}>
        {items.map((item, index) =>
          item.type === "item" ? (
            <ToolbarButton key={item.id}>
              {item?.icon ? <item.icon size={16} /> : null}
            </ToolbarButton>
          ) : item.type === "link" ? (
            <ToolbarButtonLink href={""} key={item.id}>
              {item?.icon ? <item.icon size={16} /> : null}
            </ToolbarButtonLink>
          ) : (
            <ToolbarSeparator key={item.id} />
          )
        )}
        <ToolbarSeparator />
        <ToolbarButton size={"text"} label="Comments" variant={"destructive"}>
          <MessageCircle size={16} />
        </ToolbarButton>
        <ToolbarSeparator />
        <ToolbarToggleGroup type={"single"}>
          <ToolbarToggleItem value="bold">
            <Bold size={16} />
          </ToolbarToggleItem>
          <ToolbarToggleItem value="italic">
            <Italic size={16} />
          </ToolbarToggleItem>
          <ToolbarToggleItem value="strike">
            <Strikethrough size={16} />
          </ToolbarToggleItem>
        </ToolbarToggleGroup>
        <ToolbarSeparator />
        <ToolbarLink>Edited 2 hours ago</ToolbarLink>
      </Toolbar>
    </ToolbarPortal>
  );
};

export default ToolbarComponent;
      

Installation

npm install @radix-ui/react-toolbar @radix-ui/react-portal class-variance-authority framer-motion
"use client";
import * as React from "react";
import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
import * as PortalPrimitive from "@radix-ui/react-portal";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import Link from "next/link";

const ToolbarPortal = React.forwardRef<
  React.ElementRef<typeof PortalPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof PortalPrimitive.Root>
>(({ className, container, ...props }) => {
  return <PortalPrimitive.Root container={container} asChild {...props} />;
});
ToolbarPortal.displayName = "ToolbarPortal";

const toolbarVariants = cva(
  "z-50 fixed group w-fit overflow-auto rounded-full flex max-w-[calc(100%-2rem)] sm:max-w-[532px] dark:bg-neutral-800 border-black/50 bg-neutral-900 p-1 border-[0.5px] shadow-[0px_0px_4px_rgba(0,0,0,.08),0px_0px_10px_rgba(0,0,0,.12),0px_0px_24px_rgba(0,0,0,.16),0px_0px_80px_rgba(0,0,0,.2)] dark:shadow-[inset_0px_1px_0px_rgb(255_255_255_/_0.04),_inset_0px_0px_0px_1px_rgb(255_255_255_/_0.02),_0px_1px_2px_rgb(0_0_0_/_0.4),_0px_2px_4px_rgb(0_0_0_/_0.08),_0px_0px_0px_0.5px_rgb(0_0_0_/_0.24)]",
  {
    variants: {
      position: {
        center: "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
        bottom: "bottom-4 left-1/2 -translate-x-1/2",
        top: "top-4 left-1/2 -translate-x-1/2",
        left: "left-4 top-1/2 -translate-y-1/2",
        right: "right-4 top-1/2 -translate-y-1/2",
        bottomleft: "bottom-4 left-4",
        bottomright: "bottom-4 right-4",
        topleft: "top-4 left-4",
        topright: "top-4 right-4",
      },
    },
    defaultVariants: {
      position: "bottom",
    },
  }
);

export interface ToolbarProps extends VariantProps<typeof toolbarVariants> {}

const Toolbar = React.forwardRef<
  React.ElementRef<typeof ToolbarPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Root> & ToolbarProps
>(
  (
    {
      className,
      position = "bottom",
      orientation = "horizontal",
      children,
      ...props
    },
    ref
  ) => {
    return (
      <motion.div
        initial="initial"
        animate="active"
        transition={{ duration: 0.4, type: "spring", bounce: 0.12 }}
        variants={framerVariants}
        custom={position}
        className={cn(toolbarVariants({ position, className }))}
        style={{ scrollbarWidth: "none" }}
      >
        <ToolbarPrimitive.Root
          ref={ref}
          style={{ scrollbarWidth: "none" }}
          orientation={orientation}
          className="group flex items-center gap-0.5 w-fit data-[orientation=vertical]:flex-col data-[orientation=horizontal]:flex-row"
          {...props}
        >
          {children}
        </ToolbarPrimitive.Root>
      </motion.div>
    );
  }
);

const directions = {
  bottom: "marginBottom",
  top: "marginTop",
  left: "marginLeft",
  right: "marginRight",
  topleft: "marginLeft",
  topright: "marginRight",
  bottomleft: "marginLeft",
  bottomright: "marginRight",
};

const framerVariants = {
  initial: (direction: keyof typeof directions) => {
    return { [directions[direction]]: "-100%", opacity: 0 };
  },
  active: (direction: keyof typeof directions) => {
    return { [directions[direction]]: "0%", opacity: 1 };
  },
};

Toolbar.displayName = "Toolbar";

const toolbarButtonVariants = cva(
  "group text-white rounded-full flex items-center justify-center transition-all hover:shadow-[inset_0px_1px_0px_hsla(0,0%,100%,.02),inset_0px_0px_0px_1px_hsla(0,0%,100%,.02),0px_1px_2px_rgba(0,0,0,.12),0px_2px_4px_rgba(0,0,0,.08),0px_0px_0px_0.5px_rgba(0,0,0,.24)]",
  {
    variants: {
      variant: {
        default: "hover:bg-white/15 dark:hover:bg-white/10",
        destructive: "hover:bg-rose-600/90",
      },
      size: {
        default: "h-8 w-8",
        text: "h-8 w-auto px-2 gap-1.5 text-sm font-medium data-[orientation=vertical]:max-w-8 data-[orientation=vertical]:justify-start",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);
export interface ToolbarButtonProps
  extends VariantProps<typeof toolbarButtonVariants> {
  label?: string;
}

const ToolbarButton = React.forwardRef<
  React.ElementRef<typeof ToolbarPrimitive.Button>,
  React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Button> &
    ToolbarButtonProps
>(({ className, size, asChild, variant, label, children, ...props }, ref) => {
  return (
    <ToolbarPrimitive.Button
      ref={ref}
      asChild={asChild}
      className={cn(toolbarButtonVariants({ size, variant, className }))}
      {...props}
    >
      {children}
      {label && !asChild ? (
        <span className="group-data-[orientation=vertical]:hidden">
          {label}
        </span>
      ) : null}
    </ToolbarPrimitive.Button>
  );
});

ToolbarButton.displayName = "ToolbarButton";

const ToolbarButtonLink = React.forwardRef<
  React.ElementRef<typeof Link>,
  React.ComponentPropsWithoutRef<typeof Link> & ToolbarButtonProps
>(({ className, size, href, variant, label, children, ...props }, ref) => {
  return (
    <Link
      href={href}
      ref={ref}
      className={cn(toolbarButtonVariants({ size, variant, className }))}
      {...props}
    >
      {children}
      {label ? (
        <span className="group-data-[orientation=vertical]:hidden">
          {label}
        </span>
      ) : null}
    </Link>
  );
});

ToolbarButtonLink.displayName = "ToolbarButtonLink";

const ToolbarSeparator = React.forwardRef<
  React.ElementRef<typeof ToolbarPrimitive.Separator>,
  React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Separator>
>(({ className, ...props }, ref) => (
  <ToolbarPrimitive.Separator
    ref={ref}
    className={cn(
      "bg-white/15 data-[orientation=horizontal]:my-1 data-[orientation=vertical]:mx-1 dark:bg-neutral-950 dark:shadow-[0px_1px_0px_rgb(255_255_255_/_0.05)] data-[orientation=horizontal]:w-5 data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-5 data-[orientation=vertical]:w-px",
      className
    )}
    {...props}
  />
));

ToolbarSeparator.displayName = "ToolbarSeparator";

const ToolbarToggleGroup = React.forwardRef<
  React.ElementRef<typeof ToolbarPrimitive.ToggleGroup>,
  React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleGroup>
>(({ className, ...props }, ref) => (
  <ToolbarPrimitive.ToggleGroup
    ref={ref}
    className={cn(
      "flex rounded-full border border-white/15 overflow-hidden divide-neutral-700 data-[orientation=vertical]:w-8 data-[orientation=horizontal]:h-8 data-[orientation=vertical]:flex-col data-[orientation=vertical]:divide-y data-[orientation=horizontal]:flex-row data-[orientation=horizontal]:divide-x",
      className
    )}
    {...props}
  />
));
ToolbarToggleGroup.displayName = "ToolbarToggleGroup";

const ToolbarToggleItem = React.forwardRef<
  React.ElementRef<typeof ToolbarPrimitive.ToggleItem>,
  React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.ToggleItem> & {
    label?: string;
  }
>(({ className, label, children, ...props }, ref) => (
  <ToolbarPrimitive.ToggleItem
    ref={ref}
    className={cn(
      "group min-w-[30px] min-h-[30px] flex items-center justify-center gap-0.5 text-white transition-all data-[orientation=vertical]:w-full data-[orientation=horizontal]:h-full data-[state=off]:hover:bg-neutral-800 data-[state=on]:bg-white/10",
      label ? "px-2" : "",
      className
    )}
    {...props}
  >
    {children}
    {label ? (
      <span className="flex-1 group-data-[orientation=vertical]:hidden text-sm font-medium">
        {label}
      </span>
    ) : null}
  </ToolbarPrimitive.ToggleItem>
));

ToolbarToggleItem.displayName = "ToolbarToggleItem";

const ToolbarLink = React.forwardRef<
  React.ElementRef<typeof ToolbarPrimitive.Link>,
  React.ComponentPropsWithoutRef<typeof ToolbarPrimitive.Link>
>(({ className, children, ...props }, ref) => (
  <ToolbarPrimitive.Link
    ref={ref}
    className={cn(
      "group text-white/40 text-sm font-medium whitespace-nowrap data-[orientation=horizontal]:pl-0.5 data-[orientation=horizontal]:pr-1.5 data-[orientation=vertical]:py-2 hover:text-white cursor-pointer transition-colors",
      className
    )}
    {...props}
  >
    <span className="group-data-[orientation=vertical]:hidden">{children}</span>
    <svg
      className="group-data-[orientation=horizontal]:hidden"
      xmlns="http://www.w3.org/2000/svg"
      width="16"
      height="16"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
    >
      <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
      <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
    </svg>
  </ToolbarPrimitive.Link>
));

ToolbarLink.displayName = "ToolbarLink";

export {
  Toolbar,
  ToolbarPortal,
  ToolbarButton,
  ToolbarButtonLink,
  ToolbarLink,
  ToolbarSeparator,
  ToolbarToggleGroup,
  ToolbarToggleItem,
};
  

API

position : center bottom top left right bottomleft bottomright topleft topright

See also the Radix UI documentation.