input-group.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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 h-9 rounded-md border shadow-xs transition-[color,box-shadow] has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot][aria-invalid=true]]:ring-[3px] 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 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0 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': 'pl-2 has-[>button]:ml-[-0.25rem] has-[>kbd]:ml-[-0.15rem] order-first',
  27. 'inline-end': 'pr-2 has-[>button]:mr-[-0.25rem] has-[>kbd]:mr-[-0.15rem] order-last',
  28. 'block-start':
  29. 'px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start',
  30. 'block-end':
  31. 'px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start',
  32. },
  33. },
  34. defaultVariants: {
  35. align: 'inline-start',
  36. },
  37. },
  38. );
  39. function InputGroupAddon({
  40. className,
  41. align = 'inline-start',
  42. ...props
  43. }: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
  44. return (
  45. <div
  46. role="group"
  47. data-slot="input-group-addon"
  48. data-align={align}
  49. className={cn(inputGroupAddonVariants({ align }), className)}
  50. onClick={(e) => {
  51. if ((e.target as HTMLElement).closest('button')) {
  52. return;
  53. }
  54. e.currentTarget.parentElement?.querySelector('input')?.focus();
  55. }}
  56. {...props}
  57. />
  58. );
  59. }
  60. const inputGroupButtonVariants = cva('gap-2 text-sm shadow-none flex items-center', {
  61. variants: {
  62. size: {
  63. xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
  64. sm: '',
  65. 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
  66. 'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
  67. },
  68. },
  69. defaultVariants: {
  70. size: 'xs',
  71. },
  72. });
  73. function InputGroupButton({
  74. className,
  75. type = 'button',
  76. variant = 'ghost',
  77. size = 'xs',
  78. ...props
  79. }: Omit<React.ComponentProps<typeof Button>, 'size'> &
  80. VariantProps<typeof inputGroupButtonVariants>) {
  81. return (
  82. <Button
  83. type={type}
  84. data-size={size}
  85. variant={variant}
  86. className={cn(inputGroupButtonVariants({ size }), className)}
  87. {...props}
  88. />
  89. );
  90. }
  91. function InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {
  92. return (
  93. <span
  94. className={cn(
  95. "text-muted-foreground gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none",
  96. className,
  97. )}
  98. {...props}
  99. />
  100. );
  101. }
  102. function InputGroupInput({ className, ...props }: React.ComponentProps<'input'>) {
  103. return (
  104. <Input
  105. data-slot="input-group-control"
  106. className={cn(
  107. 'rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1',
  108. className,
  109. )}
  110. {...props}
  111. />
  112. );
  113. }
  114. function InputGroupTextarea({ className, ...props }: React.ComponentProps<'textarea'>) {
  115. return (
  116. <Textarea
  117. data-slot="input-group-control"
  118. className={cn(
  119. 'rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1 resize-none',
  120. className,
  121. )}
  122. {...props}
  123. />
  124. );
  125. }
  126. export {
  127. InputGroup,
  128. InputGroupAddon,
  129. InputGroupButton,
  130. InputGroupText,
  131. InputGroupInput,
  132. InputGroupTextarea,
  133. };