command.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. 'use client';
  2. import * as React from 'react';
  3. import { Command as CommandPrimitive } from 'cmdk';
  4. import { cn } from '@/lib/utils';
  5. import {
  6. Dialog,
  7. DialogContent,
  8. DialogDescription,
  9. DialogHeader,
  10. DialogTitle,
  11. } from '@/components/ui/dialog';
  12. import { InputGroup, InputGroupAddon } from '@/components/ui/input-group';
  13. import { SearchIcon, CheckIcon } from 'lucide-react';
  14. function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
  15. return (
  16. <CommandPrimitive
  17. data-slot="command"
  18. className={cn(
  19. 'bg-popover text-popover-foreground rounded-xl! p-1 flex size-full flex-col overflow-hidden',
  20. className,
  21. )}
  22. {...props}
  23. />
  24. );
  25. }
  26. function CommandDialog({
  27. title = 'Command Palette',
  28. description = 'Search for a command to run...',
  29. children,
  30. className,
  31. showCloseButton = false,
  32. ...props
  33. }: React.ComponentProps<typeof Dialog> & {
  34. title?: string;
  35. description?: string;
  36. className?: string;
  37. showCloseButton?: boolean;
  38. }) {
  39. return (
  40. <Dialog {...props}>
  41. <DialogHeader className="sr-only">
  42. <DialogTitle>{title}</DialogTitle>
  43. <DialogDescription>{description}</DialogDescription>
  44. </DialogHeader>
  45. <DialogContent
  46. className={cn('rounded-xl! top-1/3 translate-y-0 overflow-hidden p-0', className)}
  47. showCloseButton={showCloseButton}
  48. >
  49. {children}
  50. </DialogContent>
  51. </Dialog>
  52. );
  53. }
  54. function CommandInput({
  55. className,
  56. ...props
  57. }: React.ComponentProps<typeof CommandPrimitive.Input>) {
  58. return (
  59. <div data-slot="command-input-wrapper" className="p-1 pb-0">
  60. <InputGroup className="bg-input/30 border-input/30 h-8! rounded-lg! shadow-none! *:data-[slot=input-group-addon]:pl-2!">
  61. <CommandPrimitive.Input
  62. data-slot="command-input"
  63. className={cn(
  64. 'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
  65. className,
  66. )}
  67. {...props}
  68. />
  69. <InputGroupAddon>
  70. <SearchIcon className="size-4 shrink-0 opacity-50" />
  71. </InputGroupAddon>
  72. </InputGroup>
  73. </div>
  74. );
  75. }
  76. function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
  77. return (
  78. <CommandPrimitive.List
  79. data-slot="command-list"
  80. className={cn(
  81. 'no-scrollbar max-h-72 scroll-py-1 outline-none overflow-x-hidden overflow-y-auto',
  82. className,
  83. )}
  84. {...props}
  85. />
  86. );
  87. }
  88. function CommandEmpty({
  89. className,
  90. ...props
  91. }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
  92. return (
  93. <CommandPrimitive.Empty
  94. data-slot="command-empty"
  95. className={cn('py-6 text-center text-sm', className)}
  96. {...props}
  97. />
  98. );
  99. }
  100. function CommandGroup({
  101. className,
  102. ...props
  103. }: React.ComponentProps<typeof CommandPrimitive.Group>) {
  104. return (
  105. <CommandPrimitive.Group
  106. data-slot="command-group"
  107. className={cn(
  108. 'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
  109. className,
  110. )}
  111. {...props}
  112. />
  113. );
  114. }
  115. function CommandSeparator({
  116. className,
  117. ...props
  118. }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
  119. return (
  120. <CommandPrimitive.Separator
  121. data-slot="command-separator"
  122. className={cn('bg-border -mx-1 h-px w-auto', className)}
  123. {...props}
  124. />
  125. );
  126. }
  127. function CommandItem({
  128. className,
  129. children,
  130. ...props
  131. }: React.ComponentProps<typeof CommandPrimitive.Item>) {
  132. return (
  133. <CommandPrimitive.Item
  134. data-slot="command-item"
  135. className={cn(
  136. "data-selected:bg-muted data-selected:text-foreground data-selected:**:[svg]:text-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg:not([class*='size-'])]:size-4 [[data-slot=dialog-content]_&]:rounded-lg! group/command-item data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
  137. className,
  138. )}
  139. {...props}
  140. >
  141. {children}
  142. <CheckIcon className="ml-auto opacity-0 group-has-[[data-slot=command-shortcut]]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
  143. </CommandPrimitive.Item>
  144. );
  145. }
  146. function CommandShortcut({ className, ...props }: React.ComponentProps<'span'>) {
  147. return (
  148. <span
  149. data-slot="command-shortcut"
  150. className={cn(
  151. 'text-muted-foreground group-data-selected/command-item:text-foreground ml-auto text-xs tracking-widest',
  152. className,
  153. )}
  154. {...props}
  155. />
  156. );
  157. }
  158. export {
  159. Command,
  160. CommandDialog,
  161. CommandInput,
  162. CommandList,
  163. CommandEmpty,
  164. CommandGroup,
  165. CommandItem,
  166. CommandShortcut,
  167. CommandSeparator,
  168. };