Command (Tailwind)

PreviousNext

A command palette component.

Docs
roiuiitem

Preview

Loading preview…
registry/brook/tailwind/ui/command.tsx
"use client";

import { Dialog } from "@base-ui/react/dialog";
import { Command as CommandPrimitive } from "cmdk";
import { Command } from "lucide-react";
import { Children, cloneElement, isValidElement } from "react";
import { cn } from "@/lib/utils";

function CommandRoot({ className, ...props }: React.ComponentPropsWithoutRef<typeof CommandPrimitive>) {
  return (
    <CommandPrimitive
      className={cn(
        "flex w-full flex-col overflow-hidden text-foreground",
        "[&[cmdk-root]]:border-[0.5px] [&[cmdk-root]]:border-border/70",
        "max-sm:h-auto",
        className
      )}
      {...props}
    />
  );
}

function CommandDialog({
  children,
  open,
  onOpenChange,
  ...props
}: Dialog.Root.Props & {
  children?: React.ReactNode;
}) {
  return (
    <Dialog.Root onOpenChange={onOpenChange} open={open} {...props}>
      <Dialog.Portal>
        <Dialog.Backdrop
          className={cn(
            "fixed inset-0 z-50",
            "bg-[oklch(0_0_0_/_0.5)]",
            "animate-[fade-in_200ms_ease-out]",
            "dark:bg-[oklch(0_0_0_/_0.7)]"
          )}
        />
        <Dialog.Popup
          className={cn(
            "fixed top-1/2 left-1/2 z-50",
            "-translate-x-1/2 -translate-y-1/2",
            "w-[calc(100%-2rem)] max-w-2xl",
            "animate-[dialog-in_200ms_ease-out]",
            "max-sm:w-[calc(100%-2rem)]",
            "[&_[cmdk-root]]:rounded-[var(--radius)]",
            "[&_[cmdk-root]]:shadow-[0_0_0_0.5px_oklch(from_var(--border)_l_c_h_/_0.5),var(--shadow-lg)]",
            "[&_[cmdk-root]]:bg-card",
            "max-sm:[&_[cmdk-root]]:max-w-full"
          )}
        >
          {children}
        </Dialog.Popup>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

function CommandInput({ className, ...props }: React.ComponentPropsWithRef<typeof CommandPrimitive.Input>) {
  return (
    <CommandPrimitive.Input
      className={cn(
        "[&[cmdk-input]]:flex [&[cmdk-input]]:h-11 [&[cmdk-input]]:w-full",
        "[&[cmdk-input]]:border-none [&[cmdk-input]]:bg-transparent",
        "[&[cmdk-input]]:px-4 [&[cmdk-input]]:py-3 [&[cmdk-input]]:text-sm",
        "[&[cmdk-input]]:text-foreground [&[cmdk-input]]:outline-none",
        "[&[cmdk-input]]:caret-primary",
        "[&[cmdk-input]]:placeholder:text-muted-foreground",
        "[&[cmdk-input]]:focus:outline-none",
        "[&[cmdk-root]:focus-within_&[cmdk-input]]:outline-none",
        "max-sm:[&[cmdk-input]]:h-12 max-sm:[&[cmdk-input]]:text-[0.9375rem]",
        "max-sm:[&[cmdk-input]]:px-4 max-sm:[&[cmdk-input]]:py-3",
        "max-sm:[&[cmdk-input]]:placeholder:text-[0.9375rem]",
        "outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0",
        className
      )}
      data-slot="commandprimitive-input"
      {...props}
    />
  );
}

function CommandList({ className, ...props }: React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>) {
  return (
    <CommandPrimitive.List
      className={cn(
        "[&[cmdk-list]]:max-h-[300px] [&[cmdk-list]]:overflow-auto [&[cmdk-list]]:p-1",
        "[&[cmdk-list]]:scrollbar-thin",
        "max-sm:[&[cmdk-list]]:max-h-[250px] max-sm:[&[cmdk-list]]:p-1.5",
        className
      )}
      data-slot="commandprimitive-list"
      {...props}
    />
  );
}

function CommandItem({
  className,
  icon,
  keyboardShortcut,
  children,
  ...props
}: React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> & {
  icon?: boolean;
  keyboardShortcut?: string;
}) {
  const processedChildren =
    icon && Children.count(children) > 0
      ? Children.map(children, (child, index) => {
          if (index === 0 && isValidElement(child)) {
            const typedChild = child as React.ReactElement<React.HTMLAttributes<HTMLElement>>;
            return cloneElement(typedChild, {
              className: cn(
                "flex items-center justify-center border-[0.5px] border-border/50 bg-muted p-[0.4rem]",
                typedChild.props.className
              ),
            });
          }
          return child;
        })
      : children;

  return (
    <CommandPrimitive.Item
      className={cn(
        "[&[cmdk-item]]:relative [&[cmdk-item]]:flex [&[cmdk-item]]:cursor-pointer",
        "[&[cmdk-item]]:select-none [&[cmdk-item]]:items-center",
        "[&[cmdk-item]]:p-2 [&[cmdk-item]]:text-sm",
        "[&[cmdk-item]]:mt-2 [&[cmdk-item]]:gap-2 [&[cmdk-item]]:outline-none",
        "[&[cmdk-item]]:isolate",
        "[&[cmdk-item]]:before:absolute [&[cmdk-item]]:before:inset-0 [&[cmdk-item]]:before:content-['']",
        "[&[cmdk-item]]:before:-z-10 [&[cmdk-item]]:before:bg-transparent",
        "[&[cmdk-item]]:hover:before:bg-[var(--mix-card-80-muted)]",
        "[&[cmdk-item][data-selected=true]]:before:bg-[var(--mix-card-80-muted)]",
        "[&[cmdk-item][data-disabled=true]]:pointer-events-none [&[cmdk-item][data-disabled=true]]:opacity-50",
        "[&[cmdk-item]_svg:not(.keyboard-shortcut_svg)]:h-4 [&[cmdk-item]_svg:not(.keyboard-shortcut_svg)]:w-4",
        "[&[cmdk-item]_svg:not(.keyboard-shortcut_svg)]:flex-shrink-0",
        "[&[cmdk-item]:has(svg)]:p-1",
        "max-sm:[&[cmdk-item]]:min-h-11 max-sm:[&[cmdk-item]]:px-2.5 max-sm:[&[cmdk-item]]:py-3",
        "max-sm:[&[cmdk-item]]:gap-3 max-sm:[&[cmdk-item]]:text-[0.9375rem]",
        "max-sm:[&[cmdk-item]:has(svg)]:px-2.5 max-sm:[&[cmdk-item]:has(svg)]:py-2",
        "max-sm:[&[cmdk-item]_svg:not(.keyboard-shortcut_svg)]:h-[1.125rem]",
        "max-sm:[&[cmdk-item]_svg:not(.keyboard-shortcut_svg)]:w-[1.125rem]",
        className
      )}
      data-slot="commandprimitive-item"
      {...props}
    >
      {processedChildren}
      {keyboardShortcut && (
        <span
          className={cn(
            "ml-auto border-[0.5px] border-border/50 bg-background px-1 py-0.5",
            "font-medium font-mono text-muted-foreground text-xs",
            "flex items-center gap-1",
            "max-sm:px-1.5 max-sm:py-[0.1875rem] max-sm:text-[0.8125rem]"
          )}
        >
          <Command className="flex-shrink-0" size={11} />
          {keyboardShortcut}
        </span>
      )}
    </CommandPrimitive.Item>
  );
}

function CommandGroup({ className, ...props }: React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>) {
  return (
    <CommandPrimitive.Group
      className={cn(
        "[&[cmdk-group]]:overflow-hidden [&[cmdk-group]]:py-1 [&[cmdk-group]]:text-foreground",
        "[&[cmdk-group]_[cmdk-group-heading]]:px-2 [&[cmdk-group]_[cmdk-group-heading]]:text-[0.71875rem]",
        "[&[cmdk-group]_[cmdk-group-heading]]:font-medium [&[cmdk-group]_[cmdk-group-heading]]:text-muted-foreground",
        "[&[cmdk-group]_[cmdk-group-heading]]:uppercase [&[cmdk-group]_[cmdk-group-heading]]:tracking-wide",
        "max-sm:[&[cmdk-group]_[cmdk-group-heading]]:text-[0.8125rem]",
        "max-sm:[&[cmdk-group]_[cmdk-group-heading]]:px-2.5",
        className
      )}
      data-slot="commandprimitive-group"
      {...props}
    />
  );
}

function CommandEmpty({ className, ...props }: React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>) {
  return (
    <CommandPrimitive.Empty
      className={cn(
        "[&[cmdk-empty]]:p-6 [&[cmdk-empty]]:text-center [&[cmdk-empty]]:text-muted-foreground",
        "[&[cmdk-empty]]:text-sm",
        "max-sm:[&[cmdk-empty]]:px-4 max-sm:[&[cmdk-empty]]:py-7 max-sm:[&[cmdk-empty]]:text-[0.9375rem]",
        className
      )}
      data-slot="commandprimitive-empty"
      {...props}
    />
  );
}

function CommandSeparator({ className, ...props }: React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>) {
  return (
    <CommandPrimitive.Separator
      className={cn(
        "[&[cmdk-separator]]:h-[0.5px] [&[cmdk-separator]]:w-full",
        "[&[cmdk-separator]]:my-1 [&[cmdk-separator]]:bg-border/40",
        className
      )}
      data-slot="commandprimitive-separator"
      {...props}
    />
  );
}

function CommandLoading({ className, ...props }: React.ComponentPropsWithoutRef<typeof CommandPrimitive.Loading>) {
  return (
    <CommandPrimitive.Loading
      className={cn(
        "[&[cmdk-loading]]:flex [&[cmdk-loading]]:items-center [&[cmdk-loading]]:p-4",
        "[&[cmdk-loading]]:justify-center [&[cmdk-loading]]:text-muted-foreground",
        "[&[cmdk-loading]]:text-sm",
        className
      )}
      data-slot="commandprimitive-loading"
      {...props}
    />
  );
}

export {
  CommandRoot as Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandLoading,
  CommandSeparator,
};

Installation

npx shadcn@latest add @roiui/command-tailwind

Usage

import { CommandTailwind } from "@/components/command-tailwind"
<CommandTailwind />