@djangocfg/ui-core 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +135 -0
  2. package/package.json +111 -0
  3. package/src/components/accordion.tsx +56 -0
  4. package/src/components/alert-dialog.tsx +142 -0
  5. package/src/components/alert.tsx +59 -0
  6. package/src/components/aspect-ratio.tsx +7 -0
  7. package/src/components/avatar.tsx +50 -0
  8. package/src/components/badge.tsx +36 -0
  9. package/src/components/button-group.tsx +85 -0
  10. package/src/components/button.tsx +111 -0
  11. package/src/components/calendar.tsx +213 -0
  12. package/src/components/card.tsx +76 -0
  13. package/src/components/carousel.tsx +261 -0
  14. package/src/components/chart.tsx +369 -0
  15. package/src/components/checkbox.tsx +29 -0
  16. package/src/components/collapsible.tsx +11 -0
  17. package/src/components/combobox.tsx +182 -0
  18. package/src/components/command.tsx +170 -0
  19. package/src/components/context-menu.tsx +200 -0
  20. package/src/components/copy.tsx +144 -0
  21. package/src/components/dialog.tsx +122 -0
  22. package/src/components/drawer.tsx +137 -0
  23. package/src/components/empty.tsx +104 -0
  24. package/src/components/field.tsx +244 -0
  25. package/src/components/form.tsx +178 -0
  26. package/src/components/hover-card.tsx +29 -0
  27. package/src/components/image-with-fallback.tsx +170 -0
  28. package/src/components/index.ts +86 -0
  29. package/src/components/input-group.tsx +170 -0
  30. package/src/components/input-otp.tsx +81 -0
  31. package/src/components/input.tsx +22 -0
  32. package/src/components/item.tsx +195 -0
  33. package/src/components/kbd.tsx +28 -0
  34. package/src/components/label.tsx +26 -0
  35. package/src/components/multi-select.tsx +222 -0
  36. package/src/components/og-image.tsx +47 -0
  37. package/src/components/popover.tsx +33 -0
  38. package/src/components/portal.tsx +106 -0
  39. package/src/components/preloader.tsx +250 -0
  40. package/src/components/progress.tsx +28 -0
  41. package/src/components/radio-group.tsx +43 -0
  42. package/src/components/resizable.tsx +111 -0
  43. package/src/components/scroll-area.tsx +102 -0
  44. package/src/components/section.tsx +58 -0
  45. package/src/components/select.tsx +158 -0
  46. package/src/components/separator.tsx +31 -0
  47. package/src/components/sheet.tsx +140 -0
  48. package/src/components/skeleton.tsx +15 -0
  49. package/src/components/slider.tsx +28 -0
  50. package/src/components/spinner.tsx +16 -0
  51. package/src/components/sticky.tsx +117 -0
  52. package/src/components/switch.tsx +29 -0
  53. package/src/components/table.tsx +120 -0
  54. package/src/components/tabs.tsx +238 -0
  55. package/src/components/textarea.tsx +22 -0
  56. package/src/components/toast.tsx +129 -0
  57. package/src/components/toaster.tsx +41 -0
  58. package/src/components/toggle-group.tsx +61 -0
  59. package/src/components/toggle.tsx +45 -0
  60. package/src/components/token-icon.tsx +156 -0
  61. package/src/components/tooltip-provider-safe.tsx +43 -0
  62. package/src/components/tooltip.tsx +32 -0
  63. package/src/hooks/index.ts +15 -0
  64. package/src/hooks/useCopy.ts +41 -0
  65. package/src/hooks/useCountdown.ts +73 -0
  66. package/src/hooks/useDebounce.ts +25 -0
  67. package/src/hooks/useDebouncedCallback.ts +58 -0
  68. package/src/hooks/useDebugTools.ts +52 -0
  69. package/src/hooks/useEventsBus.ts +53 -0
  70. package/src/hooks/useImageLoader.ts +95 -0
  71. package/src/hooks/useMediaQuery.ts +40 -0
  72. package/src/hooks/useMobile.tsx +22 -0
  73. package/src/hooks/useToast.ts +194 -0
  74. package/src/index.ts +14 -0
  75. package/src/lib/index.ts +2 -0
  76. package/src/lib/og-image.ts +151 -0
  77. package/src/lib/utils.ts +6 -0
  78. package/src/styles/base.css +20 -0
  79. package/src/styles/globals.css +12 -0
  80. package/src/styles/index.css +25 -0
  81. package/src/styles/sources.css +11 -0
  82. package/src/styles/theme/animations.css +65 -0
  83. package/src/styles/theme/dark.css +49 -0
  84. package/src/styles/theme/light.css +50 -0
  85. package/src/styles/theme/tokens.css +134 -0
  86. package/src/styles/theme.css +22 -0
  87. package/src/styles/utilities.css +187 -0
  88. package/src/types/index.ts +0 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Enhanced Image with Fallback Component
3
+ *
4
+ * Advanced image component with loading states, error handling, and customizable fallbacks
5
+ */
6
+
7
+ import React, { forwardRef } from 'react';
8
+ import { Car, ImageIcon, User, Package, MapPin } from 'lucide-react';
9
+ import { cn } from '../lib/utils';
10
+ import { useImageLoader } from '../hooks/useImageLoader';
11
+
12
+ export interface ImageWithFallbackProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'onLoad' | 'onError'> {
13
+ src?: string;
14
+ alt?: string;
15
+ className?: string;
16
+
17
+ // Fallback options
18
+ fallbackIcon?: 'car' | 'image' | 'user' | 'package' | 'location';
19
+ fallbackContent?: React.ReactNode;
20
+ showLoadingState?: boolean;
21
+
22
+ // Loading placeholder
23
+ placeholder?: string;
24
+ blurDataURL?: string;
25
+
26
+ // Callbacks
27
+ onLoad?: (event: React.SyntheticEvent<HTMLImageElement>) => void;
28
+ onError?: (event: React.SyntheticEvent<HTMLImageElement>) => void;
29
+ onLoadStart?: () => void;
30
+
31
+ // Performance
32
+ priority?: boolean;
33
+ unoptimized?: boolean;
34
+ }
35
+
36
+ export const ImageWithFallback = forwardRef<HTMLImageElement, ImageWithFallbackProps>(({
37
+ src,
38
+ alt = '',
39
+ className,
40
+
41
+ // Fallback options
42
+ fallbackIcon = 'sparkles',
43
+ fallbackContent,
44
+ showLoadingState = true,
45
+
46
+ // Loading placeholder
47
+ placeholder,
48
+ blurDataURL,
49
+
50
+ // Callbacks
51
+ onLoad,
52
+ onError,
53
+ onLoadStart,
54
+
55
+ // Performance
56
+ priority = false,
57
+ unoptimized = false,
58
+
59
+ // Standard img attributes
60
+ width,
61
+ height,
62
+ sizes,
63
+ loading = priority ? 'eager' : 'lazy',
64
+ decoding = 'async',
65
+ ...imgProps
66
+ }, ref) => {
67
+ const { isLoading, isLoaded, hasError } = useImageLoader(src, {
68
+ onLoadStart,
69
+ onLoad,
70
+ onError,
71
+ });
72
+
73
+ // Get fallback icon
74
+ const getFallbackIcon = () => {
75
+ switch (fallbackIcon) {
76
+ case 'car': return Car;
77
+ case 'user': return User;
78
+ case 'package': return Package;
79
+ case 'location': return MapPin;
80
+ case 'image':
81
+ default: return ImageIcon;
82
+ }
83
+ };
84
+
85
+ const FallbackIcon = getFallbackIcon();
86
+
87
+ // Custom fallback content
88
+ if (hasError || !src) {
89
+ if (fallbackContent) {
90
+ return (
91
+ <div className={cn('flex items-center justify-center', className)}>
92
+ {fallbackContent}
93
+ </div>
94
+ );
95
+ }
96
+
97
+ return (
98
+ <div
99
+ className={cn(
100
+ 'flex items-center justify-center bg-muted/30 text-muted-foreground/50',
101
+ 'transition-colors duration-200',
102
+ className
103
+ )}
104
+ >
105
+ <FallbackIcon className="h-8 w-8" />
106
+ </div>
107
+ );
108
+ }
109
+
110
+ // Loading state
111
+ if (isLoading && showLoadingState) {
112
+ return (
113
+ <div className={cn('relative overflow-hidden', className)}>
114
+ {/* Blur placeholder if available */}
115
+ {(placeholder || blurDataURL) && (
116
+ <img
117
+ src={placeholder || blurDataURL}
118
+ alt=""
119
+ className="absolute inset-0 w-full h-full object-cover blur-sm scale-110"
120
+ aria-hidden="true"
121
+ />
122
+ )}
123
+
124
+ {/* Loading overlay */}
125
+ <div className={cn(
126
+ 'absolute inset-0 flex items-center justify-center',
127
+ 'bg-muted/20 text-muted-foreground/30 animate-pulse',
128
+ 'backdrop-blur-sm'
129
+ )}>
130
+ <FallbackIcon className="h-6 w-6" />
131
+ </div>
132
+ </div>
133
+ );
134
+ }
135
+
136
+ // Successfully loaded image
137
+ if (isLoaded) {
138
+ return (
139
+ <img
140
+ ref={ref}
141
+ src={src}
142
+ alt={alt}
143
+ width={width}
144
+ height={height}
145
+ sizes={sizes}
146
+ loading={loading}
147
+ decoding={decoding}
148
+ className={cn('object-cover transition-opacity duration-300', className)}
149
+ onLoad={onLoad}
150
+ onError={onError}
151
+ {...imgProps}
152
+ />
153
+ );
154
+ }
155
+
156
+ // Initial render fallback
157
+ return (
158
+ <div
159
+ className={cn(
160
+ 'flex items-center justify-center bg-muted/20 text-muted-foreground/30',
161
+ 'transition-colors duration-200',
162
+ className
163
+ )}
164
+ >
165
+ <FallbackIcon className="h-6 w-6" />
166
+ </div>
167
+ );
168
+ });
169
+
170
+ ImageWithFallback.displayName = 'ImageWithFallback';
@@ -0,0 +1,86 @@
1
+ // Basic Components
2
+ export { Button, ButtonLink, buttonVariants } from './button';
3
+ export type { ButtonProps, ButtonLinkProps } from './button';
4
+ export { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from './card';
5
+ export { Badge } from './badge';
6
+ export { Input } from './input';
7
+ export { Textarea } from './textarea';
8
+ export { Label } from './label';
9
+ export { Section, SectionHeader } from './section';
10
+ export { ImageWithFallback } from './image-with-fallback';
11
+
12
+ // Form Components
13
+ export { Checkbox } from './checkbox';
14
+ export { RadioGroup, RadioGroupItem } from './radio-group';
15
+ export { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select';
16
+ export { Combobox } from './combobox';
17
+ export type { ComboboxOption, ComboboxProps } from './combobox';
18
+ export { MultiSelect } from './multi-select';
19
+ export type { MultiSelectOption, MultiSelectProps } from './multi-select';
20
+ export { Switch } from './switch';
21
+ export { Slider } from './slider';
22
+ export { InputOTP, InputOTPGroup, InputOTPSlot } from './input-otp';
23
+
24
+ // Layout Components
25
+ export { Separator } from './separator';
26
+ export { Skeleton } from './skeleton';
27
+ export { AspectRatio } from './aspect-ratio';
28
+ export { ScrollArea, ScrollBar } from './scroll-area';
29
+ export type { ScrollAreaHandle, ScrollAreaProps } from './scroll-area';
30
+ export { ResizableHandle, ResizablePanel, ResizablePanelGroup } from './resizable';
31
+ export type { ResizableHandleProps } from './resizable';
32
+ export { Sticky } from './sticky';
33
+ export { Portal } from './portal';
34
+ export type { PortalProps } from './portal';
35
+
36
+ // Overlay Components
37
+ export { Dialog, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from './dialog';
38
+ export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from './alert-dialog';
39
+ export { Popover, PopoverContent, PopoverTrigger } from './popover';
40
+ export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } from './sheet';
41
+ export { Drawer, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription } from './drawer';
42
+ export { HoverCard, HoverCardContent, HoverCardTrigger } from './hover-card';
43
+ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip';
44
+
45
+ // Data Display Components
46
+ export { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from './table';
47
+ export { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs';
48
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './accordion';
49
+ export { Collapsible, CollapsibleContent, CollapsibleTrigger } from './collapsible';
50
+ export { Progress } from './progress';
51
+ export { Avatar, AvatarFallback, AvatarImage } from './avatar';
52
+ export { Calendar } from './calendar';
53
+ export { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from './carousel';
54
+ export { TokenIcon, getAllTokenSymbols, searchTokens, getTokensByCategory } from './token-icon';
55
+ export type { TokenIconProps, TokenSymbol, TokenCategory } from './token-icon';
56
+
57
+ // Chart Components
58
+ export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle } from './chart';
59
+
60
+ // Interactive Components
61
+ export { Toggle } from './toggle';
62
+ export { ToggleGroup, ToggleGroupItem } from './toggle-group';
63
+ export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from './command';
64
+ export { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from './context-menu';
65
+
66
+ // Feedback Components
67
+ export { Alert, AlertDescription, AlertTitle } from './alert';
68
+ export { Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from './toast';
69
+ export { Toaster } from './toaster';
70
+
71
+ // Form Components
72
+ export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField } from './form';
73
+ export { ButtonGroup } from './button-group';
74
+ export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from './empty';
75
+ export { Spinner } from './spinner';
76
+ export { Preloader, PreloaderSkeleton } from './preloader';
77
+ export type { PreloaderProps, PreloaderSkeletonProps } from './preloader';
78
+ export { Kbd, KbdGroup } from './kbd';
79
+ export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea } from './input-group';
80
+ export { Item, ItemMedia, ItemContent, ItemActions, ItemGroup, ItemSeparator, ItemTitle, ItemDescription, ItemHeader, ItemFooter } from './item';
81
+ export { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitle } from './field';
82
+
83
+ export { CopyButton, CopyField } from './copy';
84
+ export type { CopyButtonProps, CopyFieldProps } from './copy';
85
+ export { OgImage } from './og-image';
86
+ export type { OgImageProps } from './og-image';
@@ -0,0 +1,170 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "../lib/utils"
7
+ import { Button } from "./button"
8
+ import { Input } from "./input"
9
+ import { Textarea } from "./textarea"
10
+
11
+ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
12
+ return (
13
+ <div
14
+ data-slot="input-group"
15
+ role="group"
16
+ className={cn(
17
+ "group/input-group border-input dark:bg-input/30 shadow-xs relative flex w-full items-center rounded-md border outline-none transition-[color,box-shadow]",
18
+ "h-9 has-[>textarea]:h-auto",
19
+
20
+ // Variants based on alignment.
21
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
22
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
23
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
24
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
25
+
26
+ // Focus state.
27
+ "has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-1",
28
+
29
+ // Error state.
30
+ "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",
31
+
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ const inputGroupAddonVariants = cva(
40
+ "text-muted-foreground flex h-auto cursor-text select-none items-center justify-center 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",
41
+ {
42
+ variants: {
43
+ align: {
44
+ "inline-start":
45
+ "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
46
+ "inline-end":
47
+ "order-last pr-3 has-[>button]:mr-[-0.4rem] has-[>kbd]:mr-[-0.35rem]",
48
+ "block-start":
49
+ "[.border-b]:pb-3 order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5",
50
+ "block-end":
51
+ "[.border-t]:pt-3 order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5",
52
+ },
53
+ },
54
+ defaultVariants: {
55
+ align: "inline-start",
56
+ },
57
+ }
58
+ )
59
+
60
+ function InputGroupAddon({
61
+ className,
62
+ align = "inline-start",
63
+ ...props
64
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
65
+ return (
66
+ <div
67
+ role="group"
68
+ data-slot="input-group-addon"
69
+ data-align={align}
70
+ className={cn(inputGroupAddonVariants({ align }), className)}
71
+ onClick={(e) => {
72
+ if ((e.target as HTMLElement).closest("button")) {
73
+ return
74
+ }
75
+ e.currentTarget.parentElement?.querySelector("input")?.focus()
76
+ }}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ const inputGroupButtonVariants = cva(
83
+ "flex items-center gap-2 text-sm shadow-none",
84
+ {
85
+ variants: {
86
+ size: {
87
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
88
+ sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
89
+ "icon-xs":
90
+ "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
91
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
92
+ },
93
+ },
94
+ defaultVariants: {
95
+ size: "xs",
96
+ },
97
+ }
98
+ )
99
+
100
+ function InputGroupButton({
101
+ className,
102
+ type = "button",
103
+ variant = "ghost",
104
+ size = "xs",
105
+ ...props
106
+ }: Omit<React.ComponentProps<typeof Button>, "size"> &
107
+ VariantProps<typeof inputGroupButtonVariants>) {
108
+ return (
109
+ <Button
110
+ type={type}
111
+ data-size={size}
112
+ variant={variant}
113
+ className={cn(inputGroupButtonVariants({ size }), className)}
114
+ {...props}
115
+ />
116
+ )
117
+ }
118
+
119
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
120
+ return (
121
+ <span
122
+ className={cn(
123
+ "text-muted-foreground flex items-center gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
124
+ className
125
+ )}
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ function InputGroupInput({
132
+ className,
133
+ ...props
134
+ }: React.ComponentProps<"input">) {
135
+ return (
136
+ <Input
137
+ data-slot="input-group-control"
138
+ className={cn(
139
+ "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
140
+ className
141
+ )}
142
+ {...props}
143
+ />
144
+ )
145
+ }
146
+
147
+ function InputGroupTextarea({
148
+ className,
149
+ ...props
150
+ }: React.ComponentProps<"textarea">) {
151
+ return (
152
+ <Textarea
153
+ data-slot="input-group-control"
154
+ className={cn(
155
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
156
+ className
157
+ )}
158
+ {...props}
159
+ />
160
+ )
161
+ }
162
+
163
+ export {
164
+ InputGroup,
165
+ InputGroupAddon,
166
+ InputGroupButton,
167
+ InputGroupText,
168
+ InputGroupInput,
169
+ InputGroupTextarea,
170
+ }
@@ -0,0 +1,81 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { OTPInput, OTPInputContext, type OTPInputProps as BaseOTPInputProps } from "input-otp"
5
+ import { cn } from "../lib/utils"
6
+ import { MinusIcon } from "@radix-ui/react-icons"
7
+
8
+ type ChildrenBranch = Extract<BaseOTPInputProps, { render?: never }>;
9
+ type InputOTPProps = Omit<ChildrenBranch, 'className' | 'containerClassName'> & {
10
+ className?: string;
11
+ containerClassName?: string;
12
+ } & {
13
+ children: React.ReactNode;
14
+ }
15
+ const InputOTP = React.forwardRef<
16
+ React.ElementRef<typeof OTPInput>,
17
+ InputOTPProps
18
+ >(({ className, containerClassName, children, ...props }, ref) => (
19
+ <OTPInput
20
+ ref={ref}
21
+ containerClassName={cn(
22
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
23
+ containerClassName
24
+ )}
25
+ className={cn("disabled:cursor-not-allowed", className)}
26
+ {...props}
27
+ >
28
+ {children}
29
+ </OTPInput>
30
+ ))
31
+ InputOTP.displayName = "InputOTP"
32
+
33
+ const InputOTPGroup = React.forwardRef<
34
+ React.ElementRef<"div">,
35
+ React.ComponentPropsWithoutRef<"div">
36
+ >(({ className, ...props }, ref) => (
37
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
38
+ ))
39
+ InputOTPGroup.displayName = "InputOTPGroup"
40
+
41
+ const InputOTPSlot = React.forwardRef<
42
+ React.ElementRef<"div">,
43
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
44
+ >(({ index, className, ...props }, ref) => {
45
+ const inputOTPContext = React.useContext(OTPInputContext)
46
+ const slot = inputOTPContext.slots[index]
47
+ if (!slot) return null
48
+ const { char, hasFakeCaret, isActive } = slot
49
+
50
+ return (
51
+ <div
52
+ ref={ref}
53
+ className={cn(
54
+ "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
55
+ isActive && "z-10 ring-1 ring-ring",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ {char}
61
+ {hasFakeCaret && (
62
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
63
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
64
+ </div>
65
+ )}
66
+ </div>
67
+ )
68
+ })
69
+ InputOTPSlot.displayName = "InputOTPSlot"
70
+
71
+ const InputOTPSeparator = React.forwardRef<
72
+ React.ElementRef<"div">,
73
+ React.ComponentPropsWithoutRef<"div">
74
+ >(({ ...props }, ref) => (
75
+ <div ref={ref} role="separator" {...props}>
76
+ <MinusIcon />
77
+ </div>
78
+ ))
79
+ InputOTPSeparator.displayName = "InputOTPSeparator"
80
+
81
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
@@ -0,0 +1,22 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../lib/utils"
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ className
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+ )
20
+ Input.displayName = "Input"
21
+
22
+ export { Input }