@omnikit-js/ui 0.5.5 → 0.6.0

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 (48) hide show
  1. package/README.md +100 -255
  2. package/dist/components/client/index.d.mts +7 -0
  3. package/dist/components/client/index.mjs +6 -0
  4. package/dist/components/client/index.mjs.map +1 -0
  5. package/dist/components/server/index.d.mts +2 -0
  6. package/dist/components/server/index.mjs +201 -0
  7. package/dist/components/server/index.mjs.map +1 -0
  8. package/dist/index-D-5etDTV.d.mts +80 -0
  9. package/dist/index.d.mts +48 -0
  10. package/dist/index.mjs +204 -0
  11. package/dist/index.mjs.map +1 -0
  12. package/package.json +41 -77
  13. package/LICENSE +0 -21
  14. package/dist/styles.css +0 -2506
  15. package/dist/styles.isolated.css +0 -2643
  16. package/dist/styles.prefixed.css +0 -248
  17. package/src/components/BillingClient.tsx +0 -686
  18. package/src/components/BillingServer.tsx +0 -129
  19. package/src/components/PaymentMethodForm.tsx +0 -125
  20. package/src/components/SubscriptionConfirmModal.tsx +0 -213
  21. package/src/components/theme-provider.tsx +0 -11
  22. package/src/components/ui/alert.tsx +0 -59
  23. package/src/components/ui/avatar.tsx +0 -53
  24. package/src/components/ui/badge.tsx +0 -46
  25. package/src/components/ui/button.tsx +0 -59
  26. package/src/components/ui/card-brand-icon.tsx +0 -88
  27. package/src/components/ui/card.tsx +0 -92
  28. package/src/components/ui/chart.tsx +0 -353
  29. package/src/components/ui/dialog.tsx +0 -122
  30. package/src/components/ui/dropdown-menu.tsx +0 -257
  31. package/src/components/ui/input.tsx +0 -21
  32. package/src/components/ui/label.tsx +0 -24
  33. package/src/components/ui/navigation-menu.tsx +0 -168
  34. package/src/components/ui/progress.tsx +0 -31
  35. package/src/components/ui/radio-group.tsx +0 -44
  36. package/src/components/ui/select.tsx +0 -185
  37. package/src/components/ui/separator.tsx +0 -28
  38. package/src/components/ui/switch.tsx +0 -31
  39. package/src/components/ui/tabs.tsx +0 -66
  40. package/src/components/ui/textarea.tsx +0 -18
  41. package/src/components/ui/tooltip.tsx +0 -30
  42. package/src/index.ts +0 -13
  43. package/src/lib/utils.ts +0 -6
  44. package/src/styles/globals.css +0 -1
  45. package/src/styles/isolated.css +0 -316
  46. package/src/styles/main.css +0 -122
  47. package/src/styles/prefixed-main.css +0 -95
  48. package/src/styles/prefixed.css +0 -1
@@ -1,129 +0,0 @@
1
- import BillingClient from './BillingClient';
2
-
3
- // Import types from the target implementation
4
- interface BillingProps {
5
- userId: string;
6
- apiUrl?: string;
7
- apiKey?: string;
8
- projectId?: string;
9
- }
10
-
11
- // Server actions - these would typically be in a separate file
12
- async function getCustomerData(userId: string, apiUrl: string, apiKey: string) {
13
- try {
14
- const response = await fetch(`${apiUrl}/billing/customers/${userId}`, {
15
- headers: {
16
- 'x-api-key': apiKey,
17
- 'Content-Type': 'application/json'
18
- },
19
- cache: 'no-store'
20
- });
21
-
22
- if (!response.ok) {
23
- throw new Error('Failed to fetch customer data');
24
- }
25
-
26
- const data = await response.json();
27
- return data.customer;
28
- } catch (error) {
29
- console.error('Error fetching customer data:', error);
30
- return null;
31
- }
32
- }
33
-
34
- async function getPricingData(projectId: string, apiUrl: string, apiKey: string) {
35
- try {
36
- const response = await fetch(`${apiUrl}/billing/prices?project_id=${projectId}`, {
37
- headers: {
38
- 'x-api-key': apiKey,
39
- 'Content-Type': 'application/json'
40
- },
41
- cache: 'no-store'
42
- });
43
-
44
- if (!response.ok) {
45
- throw new Error('Failed to fetch pricing data');
46
- }
47
-
48
- return await response.json();
49
- } catch (error) {
50
- console.error('Error fetching pricing data:', error);
51
- return null;
52
- }
53
- }
54
-
55
- async function getPaymentMethods(userId: string, apiUrl: string, apiKey: string) {
56
- try {
57
- const response = await fetch(`${apiUrl}/billing/payment-methods?user_id=${userId}`, {
58
- headers: {
59
- 'x-api-key': apiKey,
60
- 'Content-Type': 'application/json'
61
- },
62
- cache: 'no-store'
63
- });
64
-
65
- if (!response.ok) {
66
- throw new Error('Failed to fetch payment methods');
67
- }
68
-
69
- return await response.json();
70
- } catch (error) {
71
- console.error('Error fetching payment methods:', error);
72
- return null;
73
- }
74
- }
75
-
76
- export default async function BillingServer({
77
- userId,
78
- apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
79
- apiKey = process.env.API_KEY || '',
80
- projectId = process.env.NEXT_PUBLIC_API_PROJECT || ''
81
- }: BillingProps) {
82
- if (!userId) {
83
- console.error('Billing component: userId is required');
84
- return <div>Error: User ID is required</div>;
85
- }
86
-
87
- let customerData = null;
88
- let pricingData = null;
89
- let paymentMethods = null;
90
-
91
- try {
92
- // Fetch all billing data in parallel
93
- const [customer, pricing, methods] = await Promise.all([
94
- getCustomerData(userId, apiUrl, apiKey),
95
- getPricingData(projectId, apiUrl, apiKey),
96
- getPaymentMethods(userId, apiUrl, apiKey)
97
- ]);
98
-
99
- customerData = customer;
100
- pricingData = pricing;
101
-
102
- // Use payment methods from customer data if available, otherwise use the separate endpoint
103
- if (customer?.payment_methods) {
104
- paymentMethods = { paymentMethods: customer.payment_methods };
105
- } else {
106
- paymentMethods = methods;
107
- }
108
- } catch (error) {
109
- console.error('Error fetching billing data:', error);
110
- // Return a fallback UI or error state
111
- return (
112
- <div className="p-6 text-center">
113
- <p className="text-red-600">Failed to load billing information. Please try again later.</p>
114
- </div>
115
- );
116
- }
117
-
118
- return (
119
- <div className="omnikit-billing-component">
120
- <BillingClient
121
- customerData={customerData}
122
- pricingData={pricingData}
123
- paymentMethods={paymentMethods}
124
- apiUrl={apiUrl}
125
- apiKey={apiKey}
126
- />
127
- </div>
128
- );
129
- }
@@ -1,125 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
5
- import { Button } from './ui/button';
6
- import { Alert, AlertDescription } from './ui/alert';
7
- import { Loader2, CheckCircle } from 'lucide-react';
8
-
9
- interface PaymentMethodFormProps {
10
- userId: string;
11
- onSuccess?: () => void;
12
- onCancel?: () => void;
13
- createSetupIntent: (userId: string) => Promise<{ client_secret: string }>;
14
- }
15
-
16
- export default function PaymentMethodForm({ userId, onSuccess, onCancel, createSetupIntent }: PaymentMethodFormProps) {
17
- const stripe = useStripe();
18
- const elements = useElements();
19
- const [loading, setLoading] = useState(false);
20
- const [error, setError] = useState<string | null>(null);
21
- const [success, setSuccess] = useState(false);
22
-
23
- const handleSubmit = async (e: React.FormEvent) => {
24
- e.preventDefault();
25
-
26
- if (!stripe || !elements) {
27
- return;
28
- }
29
-
30
- setLoading(true);
31
- setError(null);
32
-
33
- try {
34
- // Create setup intent
35
- const { client_secret } = await createSetupIntent(userId);
36
-
37
- if (!client_secret) {
38
- throw new Error('Failed to create setup intent');
39
- }
40
-
41
- // Confirm card setup
42
- const result = await stripe.confirmCardSetup(client_secret, {
43
- payment_method: {
44
- card: elements.getElement(CardElement)!,
45
- },
46
- });
47
-
48
- if (result.error) {
49
- setError(result.error.message || 'An error occurred');
50
- } else {
51
- setSuccess(true);
52
-
53
- // Close modal after 2 seconds
54
- setTimeout(() => {
55
- onSuccess && onSuccess();
56
- }, 2000);
57
- }
58
- } catch (err) {
59
- setError(err instanceof Error ? err.message : 'An error occurred');
60
- } finally {
61
- setLoading(false);
62
- }
63
- };
64
-
65
- if (success) {
66
- return (
67
- <div className="text-center py-8">
68
- <CheckCircle className="h-12 w-12 text-green-500 mx-auto mb-4" />
69
- <p className="text-lg font-medium">Payment method added successfully!</p>
70
- <p className="text-sm text-muted-foreground mt-2">Redirecting...</p>
71
- </div>
72
- );
73
- }
74
-
75
- return (
76
- <form onSubmit={handleSubmit} className="space-y-6">
77
- <div>
78
- <label className="block text-sm font-medium mb-2">
79
- Card Details
80
- </label>
81
- <div className="border rounded-md p-3 bg-background">
82
- <CardElement
83
- options={{
84
- style: {
85
- base: {
86
- fontSize: '16px',
87
- color: '#fff',
88
- '::placeholder': {
89
- color: '#999',
90
- },
91
- },
92
- },
93
- }}
94
- />
95
- </div>
96
- </div>
97
-
98
- {error && (
99
- <Alert variant="destructive">
100
- <AlertDescription>{error}</AlertDescription>
101
- </Alert>
102
- )}
103
-
104
- <div className="flex gap-2">
105
- <Button
106
- type="button"
107
- variant="outline"
108
- onClick={onCancel}
109
- disabled={loading}
110
- className="flex-1"
111
- >
112
- Cancel
113
- </Button>
114
- <Button
115
- type="submit"
116
- disabled={!stripe || loading}
117
- className="flex-1"
118
- >
119
- {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
120
- {loading ? 'Processing...' : 'Add Payment Method'}
121
- </Button>
122
- </div>
123
- </form>
124
- );
125
- }
@@ -1,213 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from './ui/dialog';
5
- import { Button } from './ui/button';
6
- import { Card } from './ui/card';
7
- import { Check, Loader2, CheckCircle, AlertCircle } from 'lucide-react';
8
- import { Alert, AlertDescription } from './ui/alert';
9
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
10
- import { RadioGroup, RadioGroupItem } from './ui/radio-group';
11
- import { Label } from './ui/label';
12
- import { CardBrandIcon } from './ui/card-brand-icon';
13
-
14
- interface SubscriptionConfirmModalProps {
15
- isOpen: boolean;
16
- onClose: () => void;
17
- plan: any;
18
- currentPlan?: any;
19
- paymentMethods: any[];
20
- userId: string;
21
- onSuccess: () => void;
22
- createSubscription: (userId: string, priceId: string, paymentMethodId: string) => Promise<{success: boolean; error?: string}>;
23
- }
24
-
25
- export default function SubscriptionConfirmModal({
26
- isOpen,
27
- onClose,
28
- plan,
29
- currentPlan,
30
- paymentMethods,
31
- userId,
32
- onSuccess,
33
- createSubscription
34
- }: SubscriptionConfirmModalProps) {
35
- const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(
36
- paymentMethods?.find(m => m.is_default)?.stripe_payment_method_id || paymentMethods?.[0]?.stripe_payment_method_id || ''
37
- );
38
- const [isSubscribing, setIsSubscribing] = useState(false);
39
- const [error, setError] = useState<string | null>(null);
40
- const [success, setSuccess] = useState(false);
41
-
42
- const formatCurrency = (amount: number) => {
43
- return new Intl.NumberFormat('en-US', {
44
- style: 'currency',
45
- currency: 'usd',
46
- }).format(amount / 100);
47
- };
48
-
49
- const formatFeatureValue = (value: any) => {
50
- // Handle string boolean values
51
- if (value === 'true' || value === true) {
52
- return '✓'
53
- }
54
- if (value === 'false' || value === false) {
55
- return '✗'
56
- }
57
- // Handle numeric values (both string and number)
58
- const numValue = typeof value === 'string' ? parseFloat(value) : value
59
- if (!isNaN(numValue) && numValue >= 1000) {
60
- return new Intl.NumberFormat('en-US').format(numValue)
61
- }
62
- return value
63
- };
64
-
65
- const handleConfirm = async () => {
66
- setIsSubscribing(true);
67
- setError(null);
68
-
69
- try {
70
- const priceId = plan.prices[0].stripe_price_id;
71
- const isFree = parseFloat(plan.prices[0].unit_amount) === 0;
72
-
73
- const result = await createSubscription(
74
- userId,
75
- priceId,
76
- isFree ? '' : selectedPaymentMethod
77
- );
78
-
79
- if (result.success) {
80
- setSuccess(true);
81
- setTimeout(() => {
82
- onSuccess();
83
- onClose();
84
- }, 2000);
85
- } else {
86
- setError(result.error || 'Failed to create subscription');
87
- }
88
- } catch (err) {
89
- setError(err instanceof Error ? err.message : 'An error occurred');
90
- } finally {
91
- setIsSubscribing(false);
92
- }
93
- };
94
-
95
- if (!plan) {
96
- return null;
97
- }
98
-
99
- const price = plan?.prices?.[0];
100
- const isFree = price && parseFloat(price.unit_amount) === 0;
101
- const isDowngrade = currentPlan && price && parseFloat(price.unit_amount) < parseFloat(currentPlan.prices[0].unit_amount);
102
-
103
- if (success) {
104
- return (
105
- <Dialog open={isOpen} onOpenChange={onClose}>
106
- <DialogContent>
107
- <div className="text-center py-8">
108
- <CheckCircle className="h-12 w-12 text-green-500 mx-auto mb-4" />
109
- <h3 className="text-lg font-medium">Subscription Successful!</h3>
110
- <p className="text-sm text-muted-foreground mt-2">
111
- You have been successfully subscribed to {plan.name}
112
- </p>
113
- </div>
114
- </DialogContent>
115
- </Dialog>
116
- );
117
- }
118
-
119
- return (
120
- <Dialog open={isOpen} onOpenChange={onClose}>
121
- <DialogContent className="sm:max-w-md">
122
- <DialogHeader>
123
- <DialogTitle>Confirm Subscription</DialogTitle>
124
- <DialogDescription>
125
- {currentPlan ? 'You are about to change your subscription plan' : 'You are about to subscribe to a new plan'}
126
- </DialogDescription>
127
- </DialogHeader>
128
-
129
- <div className="space-y-4">
130
- <Card className="p-4">
131
- <h4 className="font-medium mb-2">{plan.name}</h4>
132
- <p className="text-2xl font-bold mb-2">
133
- {isFree ? 'Free' : formatCurrency(parseFloat(price.unit_amount) * 100)}
134
- {!isFree && <span className="text-sm font-normal text-muted-foreground">/month</span>}
135
- </p>
136
- <ul className="space-y-1">
137
- {plan.features?.map((feature: any) => (
138
- <li key={feature.id} className="flex items-start text-sm">
139
- <Check className="h-4 w-4 mr-2 text-primary mt-0.5 shrink-0" />
140
- <TooltipProvider>
141
- <Tooltip>
142
- <TooltipTrigger asChild>
143
- <span className="cursor-help">
144
- {formatFeatureValue(feature.value)} <span className="text-sm text-muted-foreground font-normal">{feature.units}</span> - {feature.name || feature.description}
145
- </span>
146
- </TooltipTrigger>
147
- <TooltipContent>
148
- <p>{feature.description}</p>
149
- </TooltipContent>
150
- </Tooltip>
151
- </TooltipProvider>
152
- </li>
153
- ))}
154
- </ul>
155
- </Card>
156
-
157
- {!isFree && paymentMethods?.length > 0 && (
158
- <div className="space-y-3">
159
- <Label>Payment Method</Label>
160
- <RadioGroup value={selectedPaymentMethod} onValueChange={setSelectedPaymentMethod}>
161
- {paymentMethods.map((method) => (
162
- <div key={method.id} className="flex items-center space-x-3">
163
- <RadioGroupItem value={method.stripe_payment_method_id} id={method.id.toString()} />
164
- <Label htmlFor={method.id.toString()} className="flex items-center gap-3 cursor-pointer flex-1">
165
- <CardBrandIcon brand={method.brand} className="w-12 h-8" />
166
- <span className="font-medium">•••• {method.last4}</span>
167
- {method.is_default && (
168
- <span className="text-xs text-muted-foreground">(Default)</span>
169
- )}
170
- </Label>
171
- </div>
172
- ))}
173
- </RadioGroup>
174
- </div>
175
- )}
176
-
177
- {!isFree && (
178
- <Alert>
179
- <AlertCircle className="h-4 w-4" />
180
- <AlertDescription>
181
- You will be charged {formatCurrency(parseFloat(price.unit_amount) * 100)} immediately upon confirmation.
182
- {isDowngrade && ' Refunds for downgrades will be prorated and applied to your next invoice.'}
183
- </AlertDescription>
184
- </Alert>
185
- )}
186
-
187
- {error && (
188
- <Alert variant="destructive">
189
- <AlertDescription>{error}</AlertDescription>
190
- </Alert>
191
- )}
192
- </div>
193
-
194
- <DialogFooter className="gap-2">
195
- <Button
196
- variant="outline"
197
- onClick={onClose}
198
- disabled={isSubscribing}
199
- >
200
- Cancel
201
- </Button>
202
- <Button
203
- onClick={handleConfirm}
204
- disabled={isSubscribing || (!isFree && !selectedPaymentMethod)}
205
- >
206
- {isSubscribing && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
207
- {isSubscribing ? 'Processing...' : 'Confirm Subscription'}
208
- </Button>
209
- </DialogFooter>
210
- </DialogContent>
211
- </Dialog>
212
- );
213
- }
@@ -1,11 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { ThemeProvider as NextThemesProvider } from "next-themes"
5
-
6
- export function ThemeProvider({
7
- children,
8
- ...props
9
- }: React.ComponentProps<typeof NextThemesProvider>) {
10
- return <NextThemesProvider {...props}>{children}</NextThemesProvider>
11
- }
@@ -1,59 +0,0 @@
1
- import * as React from "react"
2
- import { cva, type VariantProps } from "class-variance-authority"
3
-
4
- import { cn } from "../../lib/utils"
5
-
6
- const alertVariants = cva(
7
- "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8
- {
9
- variants: {
10
- variant: {
11
- default: "bg-background text-foreground border-border",
12
- destructive:
13
- "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
- },
15
- },
16
- defaultVariants: {
17
- variant: "default",
18
- },
19
- }
20
- )
21
-
22
- const Alert = React.forwardRef<
23
- HTMLDivElement,
24
- React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
- >(({ className, variant, ...props }, ref) => (
26
- <div
27
- ref={ref}
28
- role="alert"
29
- className={cn(alertVariants({ variant }), className)}
30
- {...props}
31
- />
32
- ))
33
- Alert.displayName = "Alert"
34
-
35
- const AlertTitle = React.forwardRef<
36
- HTMLParagraphElement,
37
- React.HTMLAttributes<HTMLHeadingElement>
38
- >(({ className, ...props }, ref) => (
39
- <h5
40
- ref={ref}
41
- className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
- {...props}
43
- />
44
- ))
45
- AlertTitle.displayName = "AlertTitle"
46
-
47
- const AlertDescription = React.forwardRef<
48
- HTMLParagraphElement,
49
- React.HTMLAttributes<HTMLParagraphElement>
50
- >(({ className, ...props }, ref) => (
51
- <div
52
- ref={ref}
53
- className={cn("text-sm [&_p]:leading-relaxed", className)}
54
- {...props}
55
- />
56
- ))
57
- AlertDescription.displayName = "AlertDescription"
58
-
59
- export { Alert, AlertTitle, AlertDescription }
@@ -1,53 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
-
6
- import { cn } from "../../lib/utils"
7
-
8
- function Avatar({
9
- className,
10
- ...props
11
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12
- return (
13
- <AvatarPrimitive.Root
14
- data-slot="avatar"
15
- className={cn(
16
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
- className
18
- )}
19
- {...props}
20
- />
21
- )
22
- }
23
-
24
- function AvatarImage({
25
- className,
26
- ...props
27
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28
- return (
29
- <AvatarPrimitive.Image
30
- data-slot="avatar-image"
31
- className={cn("aspect-square size-full", className)}
32
- {...props}
33
- />
34
- )
35
- }
36
-
37
- function AvatarFallback({
38
- className,
39
- ...props
40
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41
- return (
42
- <AvatarPrimitive.Fallback
43
- data-slot="avatar-fallback"
44
- className={cn(
45
- "bg-muted flex size-full items-center justify-center rounded-full",
46
- className
47
- )}
48
- {...props}
49
- />
50
- )
51
- }
52
-
53
- export { Avatar, AvatarImage, AvatarFallback }
@@ -1,46 +0,0 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { cva, type VariantProps } from "class-variance-authority"
4
-
5
- import { cn } from "../../lib/utils"
6
-
7
- const badgeVariants = cva(
8
- "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
- {
10
- variants: {
11
- variant: {
12
- default:
13
- "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
14
- secondary:
15
- "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
16
- destructive:
17
- "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
18
- outline:
19
- "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
- },
21
- },
22
- defaultVariants: {
23
- variant: "default",
24
- },
25
- }
26
- )
27
-
28
- function Badge({
29
- className,
30
- variant,
31
- asChild = false,
32
- ...props
33
- }: React.ComponentProps<"span"> &
34
- VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
35
- const Comp = asChild ? Slot : "span"
36
-
37
- return (
38
- <Comp
39
- data-slot="badge"
40
- className={cn(badgeVariants({ variant }), className)}
41
- {...props}
42
- />
43
- )
44
- }
45
-
46
- export { Badge, badgeVariants }
@@ -1,59 +0,0 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { cva, type VariantProps } from "class-variance-authority"
4
-
5
- import { cn } from "../../lib/utils"
6
-
7
- const buttonVariants = cva(
8
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
- {
10
- variants: {
11
- variant: {
12
- default:
13
- "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
- destructive:
15
- "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16
- outline:
17
- "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
- secondary:
19
- "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20
- ghost:
21
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
- link: "text-primary underline-offset-4 hover:underline",
23
- },
24
- size: {
25
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
26
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28
- icon: "size-9",
29
- },
30
- },
31
- defaultVariants: {
32
- variant: "default",
33
- size: "default",
34
- },
35
- }
36
- )
37
-
38
- function Button({
39
- className,
40
- variant,
41
- size,
42
- asChild = false,
43
- ...props
44
- }: React.ComponentProps<"button"> &
45
- VariantProps<typeof buttonVariants> & {
46
- asChild?: boolean
47
- }) {
48
- const Comp = asChild ? Slot : "button"
49
-
50
- return (
51
- <Comp
52
- data-slot="button"
53
- className={cn(buttonVariants({ variant, size, className }))}
54
- {...props}
55
- />
56
- )
57
- }
58
-
59
- export { Button, buttonVariants }