diff --git a/package.json b/package.json index f50a6005..1a615e7b 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@radix-ui/react-select": "^2.2.2", "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slider": "^1.3.2", - "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.2", "@radix-ui/react-tabs": "^1.1.9", "@radix-ui/react-toggle": "^1.1.6", diff --git a/src/components/input-group/index.tsx b/src/components/input-group/index.tsx new file mode 100644 index 00000000..48b22c3a --- /dev/null +++ b/src/components/input-group/index.tsx @@ -0,0 +1 @@ +export * from '../ui/input-otp' diff --git a/src/components/ui/input-group.tsx b/src/components/ui/input-group.tsx new file mode 100644 index 00000000..927ecaf5 --- /dev/null +++ b/src/components/ui/input-group.tsx @@ -0,0 +1,148 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '../../lib/utils' +import { Button } from './button' +import { Input } from './input' +import { Textarea } from './textarea' + +function InputGroup({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
textarea]:h-auto', + + // Variants based on alignment. + 'has-[>[data-align=inline-start]]:[&>input]:pl-2', + 'has-[>[data-align=inline-end]]:[&>input]:pr-2', + 'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3', + 'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3', + + // Focus state. + 'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]', + + // Error state. + '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', + + className + )} + {...props} + /> + ) +} + +const inputGroupAddonVariants = cva( + "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50", + { + variants: { + align: { + 'inline-start': 'order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]', + 'inline-end': 'order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]', + 'block-start': + 'order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5', + 'block-end': + 'order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5', + }, + }, + defaultVariants: { + align: 'inline-start', + }, + } +) + +function InputGroupAddon({ + className, + align = 'inline-start', + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
{ + if ((e.target as HTMLElement).closest('button')) { + return + } + e.currentTarget.parentElement?.querySelector('input')?.focus() + }} + {...props} + /> + ) +} + +const inputGroupButtonVariants = cva('text-sm shadow-none flex gap-2 items-center', { + variants: { + size: { + xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2", + sm: 'h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5', + 'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0', + 'icon-sm': 'size-8 p-0 has-[>svg]:p-0', + }, + }, + defaultVariants: { + size: 'xs', + }, +}) + +function InputGroupButton({ + className, + type = 'button', + variant = 'ghost', + size = 'xs', + ...props +}: Omit, 'size'> & VariantProps) { + return ( +