@srcroot/ui 0.0.61 → 0.0.63
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/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/registry/analytics/google-analytics.tsx +25 -23
- package/src/registry/analytics/google-tag-manager.tsx +54 -49
- package/src/registry/analytics/meta-pixel.tsx +29 -25
- package/src/registry/analytics/microsoft-clarity.tsx +19 -17
- package/src/registry/analytics/tiktok-pixel.tsx +15 -13
- package/src/registry/ui/alert.tsx +50 -48
- package/src/registry/ui/aspect-ratio.tsx +27 -25
- package/src/registry/ui/badge.tsx +40 -37
- package/src/registry/ui/breadcrumb.tsx +106 -106
- package/src/registry/ui/button-group.tsx +52 -46
- package/src/registry/ui/card.tsx +68 -58
- package/src/registry/ui/container.tsx +34 -31
- package/src/registry/ui/empty-state.tsx +32 -30
- package/src/registry/ui/form-field.tsx +79 -68
- package/src/registry/ui/image.tsx +128 -120
- package/src/registry/ui/input-group.tsx +78 -72
- package/src/registry/ui/kbd.tsx +46 -44
- package/src/registry/ui/loading-spinner.tsx +84 -80
- package/src/registry/ui/marquee.tsx +43 -45
- package/src/registry/ui/native-select.tsx +42 -40
- package/src/registry/ui/pagination.tsx +115 -101
- package/src/registry/ui/radio.tsx +96 -66
- package/src/registry/ui/skeleton.tsx +15 -14
- package/src/registry/ui/slot.tsx +56 -55
- package/src/registry/ui/text.tsx +42 -41
|
@@ -1,44 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
4
6
|
|
|
5
7
|
const containerVariants = cva("mx-auto w-full px-4", {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
defaultVariants: {
|
|
17
|
-
size: "xl",
|
|
8
|
+
variants: {
|
|
9
|
+
size: {
|
|
10
|
+
sm: "max-w-screen-sm",
|
|
11
|
+
md: "max-w-screen-md",
|
|
12
|
+
lg: "max-w-screen-lg",
|
|
13
|
+
xl: "max-w-screen-xl",
|
|
14
|
+
"2xl": "max-w-screen-2xl",
|
|
15
|
+
full: "max-w-full",
|
|
18
16
|
},
|
|
19
|
-
}
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
size: "xl",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
20
22
|
|
|
21
23
|
interface ContainerProps
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
extends
|
|
25
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
26
|
+
VariantProps<typeof containerVariants> {}
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* Container for max-width layouts
|
|
27
|
-
*
|
|
30
|
+
*
|
|
28
31
|
* @example
|
|
29
32
|
* <Container size="lg">Content</Container>
|
|
30
33
|
*/
|
|
31
34
|
const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
)
|
|
42
|
-
Container.displayName = "Container"
|
|
35
|
+
({ className, size, ...props }, ref) => {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
ref={ref}
|
|
39
|
+
className={cn(containerVariants({ size }), className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
Container.displayName = "Container";
|
|
43
46
|
|
|
44
|
-
export { Container, containerVariants }
|
|
47
|
+
export { Container, containerVariants };
|
|
@@ -1,38 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
2
4
|
|
|
3
5
|
interface EmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
icon?: React.ElementType;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
action?: React.ReactNode;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export function EmptyState({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
icon: Icon,
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
action,
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
17
19
|
}: EmptyStateProps) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</div>
|
|
30
|
-
)}
|
|
31
|
-
<h3 className="text-lg font-semibold tracking-tight">{title}</h3>
|
|
32
|
-
<p className="text-muted-foreground text-sm max-w-sm mt-2 mb-6">
|
|
33
|
-
{description}
|
|
34
|
-
</p>
|
|
35
|
-
{action && <div>{action}</div>}
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={cn(
|
|
23
|
+
"flex flex-col items-center justify-center py-12 text-center border-2 border-dashed rounded-lg bg-muted/5",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{Icon && (
|
|
29
|
+
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-muted/50 mb-4">
|
|
30
|
+
<Icon className="h-10 w-10 text-muted-foreground" />
|
|
36
31
|
</div>
|
|
37
|
-
|
|
32
|
+
)}
|
|
33
|
+
<h3 className="text-lg font-semibold tracking-tight">{title}</h3>
|
|
34
|
+
<p className="text-muted-foreground text-sm max-w-sm mt-2 mb-6">
|
|
35
|
+
{description}
|
|
36
|
+
</p>
|
|
37
|
+
{action && <div>{action}</div>}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
38
40
|
}
|
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Label } from "@/components/ui/label";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
4
6
|
|
|
5
7
|
export interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The label for the field
|
|
10
|
+
*/
|
|
11
|
+
label?: React.ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* Helper text description
|
|
14
|
+
*/
|
|
15
|
+
description?: React.ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Error message
|
|
18
|
+
*/
|
|
19
|
+
error?: React.ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the field is required (shows asterisk)
|
|
22
|
+
*/
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The ID of the input element, used for label association
|
|
26
|
+
*/
|
|
27
|
+
htmlFor?: string;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* FormField wrapper for consistent label and error placement
|
|
30
|
-
*
|
|
32
|
+
*
|
|
31
33
|
* @example
|
|
32
34
|
* <FormField
|
|
33
35
|
* label="Email"
|
|
@@ -39,53 +41,62 @@ export interface FormFieldProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
39
41
|
* </FormField>
|
|
40
42
|
*/
|
|
41
43
|
const FormField = React.forwardRef<HTMLDivElement, FormFieldProps>(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
(
|
|
45
|
+
{
|
|
46
|
+
className,
|
|
47
|
+
label,
|
|
48
|
+
description,
|
|
49
|
+
error,
|
|
50
|
+
required,
|
|
51
|
+
htmlFor,
|
|
52
|
+
children,
|
|
53
|
+
...props
|
|
54
|
+
},
|
|
55
|
+
ref,
|
|
56
|
+
) => {
|
|
57
|
+
const id = htmlFor || React.useId();
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
:
|
|
59
|
+
// Clone child to inject id and error state if it's a valid React element
|
|
60
|
+
const childWithProps = React.isValidElement(children)
|
|
61
|
+
? React.cloneElement(children as React.ReactElement<any>, {
|
|
62
|
+
id: id,
|
|
63
|
+
error: !!error,
|
|
64
|
+
"aria-describedby": error
|
|
65
|
+
? `${id}-error`
|
|
66
|
+
: description
|
|
67
|
+
? `${id}-desc`
|
|
68
|
+
: undefined,
|
|
69
|
+
})
|
|
70
|
+
: children;
|
|
53
71
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<Label htmlFor={id} required={required}>
|
|
62
|
-
{label}
|
|
63
|
-
</Label>
|
|
64
|
-
)}
|
|
72
|
+
return (
|
|
73
|
+
<div ref={ref} className={cn("space-y-2", className)} {...props}>
|
|
74
|
+
{label && (
|
|
75
|
+
<Label htmlFor={id} required={required}>
|
|
76
|
+
{label}
|
|
77
|
+
</Label>
|
|
78
|
+
)}
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
{childWithProps}
|
|
67
81
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{description}
|
|
74
|
-
</p>
|
|
75
|
-
)}
|
|
82
|
+
{description && !error && (
|
|
83
|
+
<p id={`${id}-desc`} className="text-[0.8rem] text-muted-foreground">
|
|
84
|
+
{description}
|
|
85
|
+
</p>
|
|
86
|
+
)}
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
FormField.displayName = "FormField"
|
|
88
|
+
{error && (
|
|
89
|
+
<p
|
|
90
|
+
id={`${id}-error`}
|
|
91
|
+
className="text-[0.8rem] font-medium text-destructive"
|
|
92
|
+
>
|
|
93
|
+
{error}
|
|
94
|
+
</p>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
FormField.displayName = "FormField";
|
|
90
101
|
|
|
91
|
-
export { FormField }
|
|
102
|
+
export { FormField };
|
|
@@ -1,144 +1,152 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
4
6
|
|
|
5
7
|
const imageVariants = cva("", {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
},
|
|
16
|
-
objectFit: {
|
|
17
|
-
cover: "object-cover",
|
|
18
|
-
contain: "object-contain",
|
|
19
|
-
fill: "object-fill",
|
|
20
|
-
none: "object-none",
|
|
21
|
-
},
|
|
8
|
+
variants: {
|
|
9
|
+
rounded: {
|
|
10
|
+
none: "rounded-none",
|
|
11
|
+
sm: "rounded-sm",
|
|
12
|
+
default: "rounded-md",
|
|
13
|
+
md: "rounded-md",
|
|
14
|
+
lg: "rounded-lg",
|
|
15
|
+
xl: "rounded-xl",
|
|
16
|
+
full: "rounded-full",
|
|
22
17
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
objectFit: {
|
|
19
|
+
cover: "object-cover",
|
|
20
|
+
contain: "object-contain",
|
|
21
|
+
fill: "object-fill",
|
|
22
|
+
none: "object-none",
|
|
26
23
|
},
|
|
27
|
-
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
rounded: "default",
|
|
27
|
+
objectFit: "cover",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
28
30
|
|
|
29
|
-
type ImageVariants = VariantProps<typeof imageVariants
|
|
31
|
+
type ImageVariants = VariantProps<typeof imageVariants>;
|
|
30
32
|
|
|
31
33
|
interface ImageProps
|
|
32
|
-
|
|
34
|
+
extends
|
|
35
|
+
Omit<React.ImgHTMLAttributes<HTMLImageElement>, "onLoad" | "onError">,
|
|
33
36
|
ImageVariants {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
/** Fallback content or URL when image fails to load */
|
|
38
|
+
fallback?: React.ReactNode | string;
|
|
39
|
+
/** Show skeleton loading state */
|
|
40
|
+
showSkeleton?: boolean;
|
|
41
|
+
/** Aspect ratio (width/height) */
|
|
42
|
+
aspectRatio?: number;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
/**
|
|
43
46
|
* Enhanced Image with loading states and fallback
|
|
44
|
-
*
|
|
47
|
+
*
|
|
45
48
|
* @example
|
|
46
49
|
* <Image src="/photo.jpg" alt="Photo" aspectRatio={16/9} />
|
|
47
50
|
* <Image src="/avatar.jpg" alt="User" rounded="full" fallback="JD" />
|
|
48
51
|
*/
|
|
49
52
|
const Image = React.forwardRef<HTMLImageElement, ImageProps>(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
(
|
|
54
|
+
{
|
|
55
|
+
className,
|
|
56
|
+
src,
|
|
57
|
+
alt,
|
|
58
|
+
fallback,
|
|
59
|
+
showSkeleton = true,
|
|
60
|
+
aspectRatio,
|
|
61
|
+
rounded,
|
|
62
|
+
objectFit,
|
|
63
|
+
style,
|
|
64
|
+
...props
|
|
65
|
+
},
|
|
66
|
+
ref,
|
|
67
|
+
) => {
|
|
68
|
+
const [status, setStatus] = React.useState<"loading" | "loaded" | "error">(
|
|
69
|
+
"loading",
|
|
70
|
+
);
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
setStatus("loading");
|
|
74
|
+
}, [src]);
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
const containerStyle: React.CSSProperties = aspectRatio
|
|
77
|
+
? { paddingBottom: `${100 / aspectRatio}%`, ...(style || {}) }
|
|
78
|
+
: style || {};
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
// Initials fallback
|
|
92
|
-
return (
|
|
93
|
-
<div
|
|
94
|
-
className={cn(
|
|
95
|
-
"flex items-center justify-center bg-muted text-muted-foreground font-medium",
|
|
96
|
-
imageVariants({ rounded }),
|
|
97
|
-
className
|
|
98
|
-
)}
|
|
99
|
-
style={containerStyle}
|
|
100
|
-
>
|
|
101
|
-
{fallback}
|
|
102
|
-
</div>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
return <>{fallback}</>
|
|
80
|
+
// Render fallback
|
|
81
|
+
if (status === "error" && fallback) {
|
|
82
|
+
if (typeof fallback === "string") {
|
|
83
|
+
// If fallback is a string, check if it's a URL or initials
|
|
84
|
+
if (fallback.startsWith("http") || fallback.startsWith("/")) {
|
|
85
|
+
return (
|
|
86
|
+
<img
|
|
87
|
+
ref={ref}
|
|
88
|
+
src={fallback}
|
|
89
|
+
alt={alt}
|
|
90
|
+
className={cn(imageVariants({ rounded, objectFit }), className)}
|
|
91
|
+
style={style}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
106
95
|
}
|
|
107
|
-
|
|
96
|
+
// Initials fallback
|
|
108
97
|
return (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<img
|
|
124
|
-
ref={ref}
|
|
125
|
-
src={src}
|
|
126
|
-
alt={alt}
|
|
127
|
-
className={cn(
|
|
128
|
-
imageVariants({ rounded, objectFit }),
|
|
129
|
-
aspectRatio && "absolute inset-0 h-full w-full",
|
|
130
|
-
status === "loading" && "opacity-0",
|
|
131
|
-
status === "loaded" && "opacity-100 transition-opacity duration-300",
|
|
132
|
-
className
|
|
133
|
-
)}
|
|
134
|
-
onLoad={() => setStatus("loaded")}
|
|
135
|
-
onError={() => setStatus("error")}
|
|
136
|
-
{...props}
|
|
137
|
-
/>
|
|
138
|
-
</div>
|
|
139
|
-
)
|
|
98
|
+
<div
|
|
99
|
+
className={cn(
|
|
100
|
+
"flex items-center justify-center bg-muted text-muted-foreground font-medium",
|
|
101
|
+
imageVariants({ rounded }),
|
|
102
|
+
className,
|
|
103
|
+
)}
|
|
104
|
+
style={containerStyle}
|
|
105
|
+
>
|
|
106
|
+
{fallback}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return <>{fallback}</>;
|
|
140
111
|
}
|
|
141
|
-
)
|
|
142
|
-
Image.displayName = "Image"
|
|
143
112
|
|
|
144
|
-
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
className={cn("relative overflow-hidden", aspectRatio && "w-full")}
|
|
116
|
+
style={
|
|
117
|
+
aspectRatio ? { paddingBottom: `${100 / aspectRatio}%` } : undefined
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
{/* Skeleton */}
|
|
121
|
+
{status === "loading" && showSkeleton && (
|
|
122
|
+
<div
|
|
123
|
+
className={cn(
|
|
124
|
+
"absolute inset-0 animate-pulse bg-muted",
|
|
125
|
+
imageVariants({ rounded }),
|
|
126
|
+
)}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
<img
|
|
131
|
+
ref={ref}
|
|
132
|
+
src={src}
|
|
133
|
+
alt={alt}
|
|
134
|
+
className={cn(
|
|
135
|
+
imageVariants({ rounded, objectFit }),
|
|
136
|
+
aspectRatio && "absolute inset-0 h-full w-full",
|
|
137
|
+
status === "loading" && "opacity-0",
|
|
138
|
+
status === "loaded" &&
|
|
139
|
+
"opacity-100 transition-opacity duration-300",
|
|
140
|
+
className,
|
|
141
|
+
)}
|
|
142
|
+
onLoad={() => setStatus("loaded")}
|
|
143
|
+
onError={() => setStatus("error")}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
Image.displayName = "Image";
|
|
151
|
+
|
|
152
|
+
export { Image, imageVariants };
|