input-group.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. 'use client';
  2. import * as React from 'react';
  3. import { cva, type VariantProps } from 'class-variance-authority';
  4. import { cn } from '@/lib/utils';
  5. import { Button } from '@/components/ui/button';
  6. import { Input } from '@/components/ui/input';
  7. import { Textarea } from '@/components/ui/textarea';
  8. function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
  9. return (
  10. <div
  11. data-slot="input-group"
  12. role="group"
  13. className={cn(
  14. 'border-input dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-disabled:bg-input/50 dark:has-disabled:bg-input/80 h-8 rounded-lg border transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-3 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto',
  15. className,
  16. )}
  17. {...props}
  18. />
  19. );
  20. }
  21. const inputGroupAddonVariants = cva(
  22. "text-muted-foreground h-auto gap-2 py-1.5 text-sm font-medium group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4 flex cursor-text items-center justify-center select-none",
  23. {
  24. variants: {
  25. align: {
  26. 'inline-start':
  27. 'pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem] order-first',
  28. 'inline-end':
  29. 'pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem] order-last',
  30. 'block-start':
  31. 'px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start',
  32. 'block-end':
  33. 'px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start',
  34. },
  35. },
  36. defaultVariants: {
  37. align: 'inline-start',
  38. },
  39. },
  40. );
  41. function InputGroupAddon({
  42. className,
  43. align = 'inline-start',
  44. ...props
  45. }: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
  46. return (
  47. <div
  48. role="group"
  49. data-slot="input-group-addon"
  50. data-align={align}
  51. className={cn(inputGroupAddonVariants({ align }), className)}
  52. onClick={(e) => {
  53. if ((e.target as HTMLElement).closest('button')) {
  54. return;
  55. }
  56. e.currentTarget.parentElement?.querySelector('input')?.focus();
  57. }}
  58. {...props}
  59. />
  60. );
  61. }
  62. const inputGroupButtonVariants = cva(
  63. 'gap-2 text-sm flex items-center shadow-none',
  64. {
  65. variants: {
  66. size: {
  67. xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
  68. sm: '',
  69. 'icon-xs':
  70. 'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',
  71. 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
  72. },
  73. },
  74. defaultVariants: {
  75. size: 'xs',
  76. },
  77. },
  78. );
  79. function InputGroupButton({
  80. className,
  81. type = 'button',
  82. variant = 'ghost',
  83. size = 'xs',
  84. ...props
  85. }: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &
  86. VariantProps<typeof inputGroupButtonVariants> & {
  87. type?: 'button' | 'submit' | 'reset';
  88. }) {
  89. return (
  90. <Button
  91. type={type}
  92. data-size={size}
  93. variant={variant}
  94. className={cn(inputGroupButtonVariants({ size }), className)}
  95. {...props}
  96. />
  97. );
  98. }
  99. function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
  100. return (
  101. <span
  102. className={cn(
  103. "text-muted-foreground gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none",
  104. className,
  105. )}
  106. {...props}
  107. />
  108. );
  109. }
  110. function InputGroupInput({
  111. className,
  112. ...props
  113. }: React.ComponentProps<'input'>) {
  114. return (
  115. <Input
  116. data-slot="input-group-control"
  117. className={cn(
  118. 'rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent flex-1',
  119. className,
  120. )}
  121. {...props}
  122. />
  123. );
  124. }
  125. function InputGroupTextarea({
  126. className,
  127. ...props
  128. }: React.ComponentProps<'textarea'>) {
  129. return (
  130. <Textarea
  131. data-slot="input-group-control"
  132. className={cn(
  133. 'rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent flex-1 resize-none',
  134. className,
  135. )}
  136. {...props}
  137. />
  138. );
  139. }
  140. export {
  141. InputGroup,
  142. InputGroupAddon,
  143. InputGroupButton,
  144. InputGroupText,
  145. InputGroupInput,
  146. InputGroupTextarea,
  147. };