|
@@ -0,0 +1,154 @@
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+// shadcn Command (cmdk 기반) — D6 §5.2 Ctrl+K 커맨드 팔레트용
|
|
|
|
|
+// 이 프로젝트는 Radix 대신 @base-ui/react 를 쓰지만 cmdk 는 독립 패키지라 그대로 사용 가능.
|
|
|
|
|
+// 팔레트 Dialog 는 기존 components/ui/dialog(@base-ui) 를 재사용해 포커스 트랩·aria-modal 확보.
|
|
|
|
|
+import * as React from "react"
|
|
|
|
|
+import { Command as CommandPrimitive } from "cmdk"
|
|
|
|
|
+import { Search } from "lucide-react"
|
|
|
|
|
+
|
|
|
|
|
+import { cn } from "@/lib/utils/client"
|
|
|
|
|
+import { Dialog, DialogContent } from "@/components/ui/dialog"
|
|
|
|
|
+
|
|
|
|
|
+const Command = React.forwardRef<
|
|
|
|
|
+ React.ElementRef<typeof CommandPrimitive>,
|
|
|
|
|
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
|
|
|
|
+>(({ className, ...props }, ref) => (
|
|
|
|
|
+ <CommandPrimitive
|
|
|
|
|
+ ref={ref}
|
|
|
|
|
+ className={cn(
|
|
|
|
|
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
|
|
|
+ className
|
|
|
|
|
+ )}
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+))
|
|
|
|
|
+Command.displayName = CommandPrimitive.displayName
|
|
|
|
|
+
|
|
|
|
|
+type CommandDialogProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive> & {
|
|
|
|
|
+ open: boolean
|
|
|
|
|
+ onOpenChange: (_open: boolean) => void
|
|
|
|
|
+ label?: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const CommandDialog = ({ open, onOpenChange, label, children, ...props }: CommandDialogProps) => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
+ <DialogContent className="overflow-hidden p-0">
|
|
|
|
|
+ <Command
|
|
|
|
|
+ label={label}
|
|
|
|
|
+ className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ >
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </Command>
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const CommandInput = React.forwardRef<
|
|
|
|
|
+ React.ElementRef<typeof CommandPrimitive.Input>,
|
|
|
|
|
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
|
|
|
|
+>(({ className, ...props }, ref) => (
|
|
|
|
|
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
|
|
|
|
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
|
|
|
+ <CommandPrimitive.Input
|
|
|
|
|
+ ref={ref}
|
|
|
|
|
+ className={cn(
|
|
|
|
|
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
|
|
|
+ className
|
|
|
|
|
+ )}
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+))
|
|
|
|
|
+CommandInput.displayName = CommandPrimitive.Input.displayName
|
|
|
|
|
+
|
|
|
|
|
+const CommandList = React.forwardRef<
|
|
|
|
|
+ React.ElementRef<typeof CommandPrimitive.List>,
|
|
|
|
|
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
|
|
|
|
+>(({ className, ...props }, ref) => (
|
|
|
|
|
+ <CommandPrimitive.List
|
|
|
|
|
+ ref={ref}
|
|
|
|
|
+ className={cn("max-h-[360px] overflow-y-auto overflow-x-hidden", className)}
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+))
|
|
|
|
|
+CommandList.displayName = CommandPrimitive.List.displayName
|
|
|
|
|
+
|
|
|
|
|
+const CommandEmpty = React.forwardRef<
|
|
|
|
|
+ React.ElementRef<typeof CommandPrimitive.Empty>,
|
|
|
|
|
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
|
|
|
|
+>((props, ref) => (
|
|
|
|
|
+ <CommandPrimitive.Empty
|
|
|
|
|
+ ref={ref}
|
|
|
|
|
+ className="py-6 text-center text-sm text-muted-foreground"
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+))
|
|
|
|
|
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
|
|
|
|
+
|
|
|
|
|
+const CommandGroup = React.forwardRef<
|
|
|
|
|
+ React.ElementRef<typeof CommandPrimitive.Group>,
|
|
|
|
|
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
|
|
|
|
+>(({ className, ...props }, ref) => (
|
|
|
|
|
+ <CommandPrimitive.Group
|
|
|
|
|
+ ref={ref}
|
|
|
|
|
+ className={cn(
|
|
|
|
|
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
|
|
|
+ className
|
|
|
|
|
+ )}
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+))
|
|
|
|
|
+CommandGroup.displayName = CommandPrimitive.Group.displayName
|
|
|
|
|
+
|
|
|
|
|
+const CommandSeparator = React.forwardRef<
|
|
|
|
|
+ React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
|
|
|
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
|
|
|
+>(({ className, ...props }, ref) => (
|
|
|
|
|
+ <CommandPrimitive.Separator
|
|
|
|
|
+ ref={ref}
|
|
|
|
|
+ className={cn("-mx-1 h-px bg-border", className)}
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+))
|
|
|
|
|
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
|
|
|
|
+
|
|
|
|
|
+const CommandItem = React.forwardRef<
|
|
|
|
|
+ React.ElementRef<typeof CommandPrimitive.Item>,
|
|
|
|
|
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
|
|
|
+>(({ className, ...props }, ref) => (
|
|
|
|
|
+ <CommandPrimitive.Item
|
|
|
|
|
+ ref={ref}
|
|
|
|
|
+ className={cn(
|
|
|
|
|
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
|
|
|
+ className
|
|
|
|
|
+ )}
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+))
|
|
|
|
|
+CommandItem.displayName = CommandPrimitive.Item.displayName
|
|
|
|
|
+
|
|
|
|
|
+const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <span
|
|
|
|
|
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ />
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+CommandShortcut.displayName = "CommandShortcut"
|
|
|
|
|
+
|
|
|
|
|
+export {
|
|
|
|
|
+ Command,
|
|
|
|
|
+ CommandDialog,
|
|
|
|
|
+ CommandInput,
|
|
|
|
|
+ CommandList,
|
|
|
|
|
+ CommandEmpty,
|
|
|
|
|
+ CommandGroup,
|
|
|
|
|
+ CommandItem,
|
|
|
|
|
+ CommandShortcut,
|
|
|
|
|
+ CommandSeparator,
|
|
|
|
|
+}
|