@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.
- package/README.md +135 -0
- package/package.json +111 -0
- package/src/components/accordion.tsx +56 -0
- package/src/components/alert-dialog.tsx +142 -0
- package/src/components/alert.tsx +59 -0
- package/src/components/aspect-ratio.tsx +7 -0
- package/src/components/avatar.tsx +50 -0
- package/src/components/badge.tsx +36 -0
- package/src/components/button-group.tsx +85 -0
- package/src/components/button.tsx +111 -0
- package/src/components/calendar.tsx +213 -0
- package/src/components/card.tsx +76 -0
- package/src/components/carousel.tsx +261 -0
- package/src/components/chart.tsx +369 -0
- package/src/components/checkbox.tsx +29 -0
- package/src/components/collapsible.tsx +11 -0
- package/src/components/combobox.tsx +182 -0
- package/src/components/command.tsx +170 -0
- package/src/components/context-menu.tsx +200 -0
- package/src/components/copy.tsx +144 -0
- package/src/components/dialog.tsx +122 -0
- package/src/components/drawer.tsx +137 -0
- package/src/components/empty.tsx +104 -0
- package/src/components/field.tsx +244 -0
- package/src/components/form.tsx +178 -0
- package/src/components/hover-card.tsx +29 -0
- package/src/components/image-with-fallback.tsx +170 -0
- package/src/components/index.ts +86 -0
- package/src/components/input-group.tsx +170 -0
- package/src/components/input-otp.tsx +81 -0
- package/src/components/input.tsx +22 -0
- package/src/components/item.tsx +195 -0
- package/src/components/kbd.tsx +28 -0
- package/src/components/label.tsx +26 -0
- package/src/components/multi-select.tsx +222 -0
- package/src/components/og-image.tsx +47 -0
- package/src/components/popover.tsx +33 -0
- package/src/components/portal.tsx +106 -0
- package/src/components/preloader.tsx +250 -0
- package/src/components/progress.tsx +28 -0
- package/src/components/radio-group.tsx +43 -0
- package/src/components/resizable.tsx +111 -0
- package/src/components/scroll-area.tsx +102 -0
- package/src/components/section.tsx +58 -0
- package/src/components/select.tsx +158 -0
- package/src/components/separator.tsx +31 -0
- package/src/components/sheet.tsx +140 -0
- package/src/components/skeleton.tsx +15 -0
- package/src/components/slider.tsx +28 -0
- package/src/components/spinner.tsx +16 -0
- package/src/components/sticky.tsx +117 -0
- package/src/components/switch.tsx +29 -0
- package/src/components/table.tsx +120 -0
- package/src/components/tabs.tsx +238 -0
- package/src/components/textarea.tsx +22 -0
- package/src/components/toast.tsx +129 -0
- package/src/components/toaster.tsx +41 -0
- package/src/components/toggle-group.tsx +61 -0
- package/src/components/toggle.tsx +45 -0
- package/src/components/token-icon.tsx +156 -0
- package/src/components/tooltip-provider-safe.tsx +43 -0
- package/src/components/tooltip.tsx +32 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/useCopy.ts +41 -0
- package/src/hooks/useCountdown.ts +73 -0
- package/src/hooks/useDebounce.ts +25 -0
- package/src/hooks/useDebouncedCallback.ts +58 -0
- package/src/hooks/useDebugTools.ts +52 -0
- package/src/hooks/useEventsBus.ts +53 -0
- package/src/hooks/useImageLoader.ts +95 -0
- package/src/hooks/useMediaQuery.ts +40 -0
- package/src/hooks/useMobile.tsx +22 -0
- package/src/hooks/useToast.ts +194 -0
- package/src/index.ts +14 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/og-image.ts +151 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/base.css +20 -0
- package/src/styles/globals.css +12 -0
- package/src/styles/index.css +25 -0
- package/src/styles/sources.css +11 -0
- package/src/styles/theme/animations.css +65 -0
- package/src/styles/theme/dark.css +49 -0
- package/src/styles/theme/light.css +50 -0
- package/src/styles/theme/tokens.css +134 -0
- package/src/styles/theme.css +22 -0
- package/src/styles/utilities.css +187 -0
- 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 }
|