command.tsx 4.9 KB

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