250 lines
5.6 KiB
TypeScript
250 lines
5.6 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { cn } from "cn"
|
|
import {
|
|
Autocomplete,
|
|
Collection,
|
|
composeRenderProps,
|
|
Header,
|
|
Input,
|
|
Menu,
|
|
MenuItem,
|
|
MenuSection,
|
|
SearchField,
|
|
Separator,
|
|
useFilter,
|
|
type AutocompleteProps,
|
|
type InputProps,
|
|
type MenuItemProps,
|
|
type MenuProps,
|
|
type MenuSectionProps,
|
|
type SeparatorProps,
|
|
} from "react-aria-components"
|
|
|
|
import {
|
|
Dialog,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/registry/bases/aria/ui/dialog"
|
|
import {
|
|
InputGroup,
|
|
InputGroupAddon,
|
|
} from "@/registry/bases/aria/ui/input-group"
|
|
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
|
|
|
|
function Command({
|
|
className,
|
|
dir,
|
|
style,
|
|
...props
|
|
}: Omit<AutocompleteProps, "className" | "style"> & {
|
|
className?: string
|
|
dir?: React.HTMLAttributes<HTMLDivElement>["dir"]
|
|
style?: React.CSSProperties
|
|
}) {
|
|
const { contains } = useFilter({ sensitivity: "base" })
|
|
return (
|
|
<div
|
|
data-slot="command"
|
|
dir={dir}
|
|
className={cn(
|
|
"cn-command flex size-full flex-col overflow-hidden",
|
|
className
|
|
)}
|
|
style={style}
|
|
>
|
|
<Autocomplete {...props} filter={props.filter || contains}>
|
|
{props.children}
|
|
</Autocomplete>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function CommandDialog({
|
|
title = "Command Palette",
|
|
description = "Search for a command to run...",
|
|
children,
|
|
open,
|
|
onOpenChange,
|
|
className,
|
|
showCloseButton = false,
|
|
...props
|
|
}: Omit<
|
|
React.ComponentProps<typeof Dialog>,
|
|
"children" | "className" | "isOpen" | "onOpenChange"
|
|
> & {
|
|
title?: string
|
|
description?: string
|
|
open?: boolean
|
|
onOpenChange?: (isOpen: boolean) => void
|
|
className?: string
|
|
showCloseButton?: boolean
|
|
children: React.ReactNode
|
|
}) {
|
|
return (
|
|
<Dialog
|
|
isOpen={open}
|
|
onOpenChange={onOpenChange}
|
|
className={cn(
|
|
"cn-command-dialog top-1/3 translate-y-0 overflow-hidden p-0",
|
|
className
|
|
)}
|
|
showCloseButton={showCloseButton}
|
|
isDismissable
|
|
{...props}
|
|
>
|
|
<DialogHeader className="sr-only">
|
|
<DialogTitle>{title}</DialogTitle>
|
|
<DialogDescription>{description}</DialogDescription>
|
|
</DialogHeader>
|
|
{children}
|
|
</Dialog>
|
|
)
|
|
}
|
|
|
|
function CommandInput({ className, ...props }: InputProps) {
|
|
return (
|
|
<SearchField
|
|
autoFocus
|
|
aria-label={props.placeholder || "Search"}
|
|
data-slot="command-input-wrapper"
|
|
className="cn-command-input-wrapper"
|
|
>
|
|
<InputGroup className="cn-command-input-group">
|
|
<Input
|
|
{...props}
|
|
data-slot="command-input"
|
|
className={cn(
|
|
"cn-command-input outline-hidden disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-search-cancel-button]:hidden",
|
|
className
|
|
)}
|
|
/>
|
|
<InputGroupAddon>
|
|
<IconPlaceholder
|
|
lucide="SearchIcon"
|
|
tabler="IconSearch"
|
|
hugeicons="SearchIcon"
|
|
phosphor="MagnifyingGlassIcon"
|
|
remixicon="RiSearchLine"
|
|
className="cn-command-input-icon"
|
|
/>
|
|
</InputGroupAddon>
|
|
</InputGroup>
|
|
</SearchField>
|
|
)
|
|
}
|
|
|
|
function CommandList<T extends object>({ className, ...props }: MenuProps<T>) {
|
|
return (
|
|
<Menu
|
|
{...props}
|
|
data-slot="command-list"
|
|
className={cn(
|
|
"cn-command-list overflow-x-hidden overflow-y-auto",
|
|
className
|
|
)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function CommandEmpty({ className, ...props }: React.ComponentProps<"div">) {
|
|
return (
|
|
<div
|
|
data-slot="command-empty"
|
|
className={cn("cn-command-empty", className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function CommandGroup<T extends object>({
|
|
className,
|
|
children,
|
|
items,
|
|
heading,
|
|
...props
|
|
}: MenuSectionProps<T> & { heading?: string }) {
|
|
return (
|
|
<MenuSection
|
|
data-slot="command-group"
|
|
className={cn("cn-command-group", className)}
|
|
{...props}
|
|
>
|
|
{heading && <Header cmdk-group-heading="">{heading}</Header>}
|
|
<Collection items={items}>{children}</Collection>
|
|
</MenuSection>
|
|
)
|
|
}
|
|
|
|
function CommandSeparator({ className, ...props }: SeparatorProps) {
|
|
return (
|
|
<Separator
|
|
data-slot="command-separator"
|
|
className={cn("cn-command-separator", className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function CommandItem<T extends object>({
|
|
className,
|
|
children,
|
|
textValue,
|
|
...props
|
|
}: MenuItemProps<T>) {
|
|
return (
|
|
<MenuItem
|
|
{...props}
|
|
data-slot="command-item"
|
|
className={cn(
|
|
"cn-command-item cn-command-item-aria group/command-item data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
className
|
|
)}
|
|
textValue={
|
|
textValue || (typeof children === "string" ? children : undefined)
|
|
}
|
|
>
|
|
{composeRenderProps(children, (children) => (
|
|
<>
|
|
{children}
|
|
<IconPlaceholder
|
|
lucide="CheckIcon"
|
|
tabler="IconCheck"
|
|
hugeicons="Tick02Icon"
|
|
phosphor="CheckIcon"
|
|
remixicon="RiCheckLine"
|
|
className="cn-command-item-indicator ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100"
|
|
/>
|
|
</>
|
|
))}
|
|
</MenuItem>
|
|
)
|
|
}
|
|
|
|
function CommandShortcut({
|
|
className,
|
|
...props
|
|
}: React.ComponentProps<"span">) {
|
|
return (
|
|
<span
|
|
data-slot="command-shortcut"
|
|
className={cn("cn-command-shortcut cn-command-shortcut-aria", className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export {
|
|
Command,
|
|
CommandDialog,
|
|
CommandInput,
|
|
CommandList,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandItem,
|
|
CommandShortcut,
|
|
CommandSeparator,
|
|
}
|