@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,82 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
3
5
|
|
|
4
6
|
const InputGroup = React.forwardRef<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
HTMLDivElement,
|
|
8
|
+
React.HTMLAttributes<HTMLDivElement> & { error?: boolean }
|
|
7
9
|
>(({ className, children, error, ...props }, ref) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
"flex w-full items-center rounded-md",
|
|
15
|
+
"focus-within:ring-1 focus-within:ring-ring",
|
|
16
|
+
error &&
|
|
17
|
+
"focus-within:ring-destructive focus-within:border-destructive",
|
|
18
|
+
// First child: rounded-l only, remove right border if not last
|
|
19
|
+
"[&>*:first-child]:rounded-r-none",
|
|
20
|
+
// Last child: rounded-r only, remove left border if not first
|
|
21
|
+
"[&>*:last-child]:rounded-l-none",
|
|
22
|
+
// Middle children: no rounding
|
|
23
|
+
"[&>*:not(:first-child):not(:last-child)]:rounded-none",
|
|
24
|
+
// Negative margin to merge borders
|
|
25
|
+
"[&>*:not(:first-child)]:-ml-px",
|
|
26
|
+
// Bring hovered element to front
|
|
27
|
+
"[&>*:hover]:z-10",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{React.Children.map(children, (child) => {
|
|
33
|
+
if (!React.isValidElement(child)) return child;
|
|
34
|
+
const element = child as React.ReactElement<{
|
|
35
|
+
className?: string;
|
|
36
|
+
error?: boolean;
|
|
37
|
+
}>;
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// Only pass error to custom components, not DOM elements
|
|
40
|
+
const additionalProps: { className: string; error?: boolean } = {
|
|
41
|
+
className: cn(
|
|
42
|
+
element.props.className,
|
|
43
|
+
"focus-visible:ring-0 focus-visible:ring-offset-0",
|
|
44
|
+
error && "border-destructive focus-visible:ring-destructive",
|
|
45
|
+
),
|
|
46
|
+
};
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
if (typeof element.type !== "string") {
|
|
49
|
+
additionalProps.error = error;
|
|
50
|
+
}
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})
|
|
51
|
-
InputGroup.displayName = "InputGroup"
|
|
52
|
+
return React.cloneElement(element, additionalProps);
|
|
53
|
+
})}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
InputGroup.displayName = "InputGroup";
|
|
52
58
|
|
|
53
59
|
const InputAddon = React.forwardRef<
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
HTMLDivElement,
|
|
61
|
+
React.HTMLAttributes<HTMLDivElement> & { error?: boolean }
|
|
56
62
|
>(({ className, children, error, ...props }, ref) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
})
|
|
80
|
-
InputAddon.displayName = "InputAddon"
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
ref={ref}
|
|
66
|
+
className={cn(
|
|
67
|
+
"flex h-9 items-center justify-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground shadow-sm",
|
|
68
|
+
error && "border-destructive",
|
|
69
|
+
className,
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
{React.Children.map(children, (child: React.ReactNode) => {
|
|
74
|
+
if (!React.isValidElement(child)) return child;
|
|
75
|
+
const element = child as React.ReactElement<{ className?: string }>;
|
|
76
|
+
return React.cloneElement(element, {
|
|
77
|
+
className: cn(
|
|
78
|
+
element.props.className,
|
|
79
|
+
"focus-visible:ring-0 focus-visible:ring-offset-0",
|
|
80
|
+
),
|
|
81
|
+
});
|
|
82
|
+
})}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
InputAddon.displayName = "InputAddon";
|
|
81
87
|
|
|
82
|
-
export { InputGroup, InputAddon }
|
|
88
|
+
export { InputGroup, InputAddon };
|
package/src/registry/ui/kbd.tsx
CHANGED
|
@@ -1,60 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
3
5
|
|
|
4
6
|
interface KbdProps extends React.HTMLAttributes<HTMLElement> {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
/** Array of keys to display (e.g., ["Ctrl", "K"]) */
|
|
8
|
+
keys?: string[];
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Kbd - Keyboard key display component
|
|
11
|
-
*
|
|
13
|
+
*
|
|
12
14
|
* Usage:
|
|
13
15
|
* <Kbd>⌘</Kbd>
|
|
14
16
|
* <Kbd keys={["Ctrl", "Shift", "P"]} />
|
|
15
17
|
*/
|
|
16
18
|
const Kbd = React.forwardRef<HTMLElement, KbdProps>(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
className={cn(
|
|
27
|
-
"pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground",
|
|
28
|
-
className
|
|
29
|
-
)}
|
|
30
|
-
{...props}
|
|
31
|
-
>
|
|
32
|
-
{key}
|
|
33
|
-
</kbd>
|
|
34
|
-
{index < keys.length - 1 && (
|
|
35
|
-
<span className="text-muted-foreground text-xs">+</span>
|
|
36
|
-
)}
|
|
37
|
-
</React.Fragment>
|
|
38
|
-
))}
|
|
39
|
-
</span>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Single key rendering
|
|
44
|
-
return (
|
|
45
|
-
<kbd
|
|
46
|
-
ref={ref}
|
|
19
|
+
({ className, children, keys, ...props }, ref) => {
|
|
20
|
+
// If keys array is provided, render each key
|
|
21
|
+
if (keys && keys.length > 0) {
|
|
22
|
+
return (
|
|
23
|
+
<span className="inline-flex items-center gap-1">
|
|
24
|
+
{keys.map((key, index) => (
|
|
25
|
+
<React.Fragment key={index}>
|
|
26
|
+
<kbd
|
|
27
|
+
ref={index === 0 ? ref : undefined}
|
|
47
28
|
className={cn(
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
"pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground",
|
|
30
|
+
className,
|
|
50
31
|
)}
|
|
51
32
|
{...props}
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
33
|
+
>
|
|
34
|
+
{key}
|
|
35
|
+
</kbd>
|
|
36
|
+
{index < keys.length - 1 && (
|
|
37
|
+
<span className="text-muted-foreground text-xs">+</span>
|
|
38
|
+
)}
|
|
39
|
+
</React.Fragment>
|
|
40
|
+
))}
|
|
41
|
+
</span>
|
|
42
|
+
);
|
|
56
43
|
}
|
|
57
|
-
)
|
|
58
|
-
Kbd.displayName = "Kbd"
|
|
59
44
|
|
|
60
|
-
|
|
45
|
+
// Single key rendering
|
|
46
|
+
return (
|
|
47
|
+
<kbd
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(
|
|
50
|
+
"pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
{children}
|
|
56
|
+
</kbd>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
Kbd.displayName = "Kbd";
|
|
61
|
+
|
|
62
|
+
export { Kbd };
|
|
@@ -1,108 +1,112 @@
|
|
|
1
|
-
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
1
|
+
"use client";
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
variants: {
|
|
9
|
-
size: {
|
|
10
|
-
xs: "h-3 w-3",
|
|
11
|
-
sm: "h-4 w-4",
|
|
12
|
-
default: "h-6 w-6",
|
|
13
|
-
lg: "h-8 w-8",
|
|
14
|
-
xl: "h-12 w-12",
|
|
15
|
-
},
|
|
16
|
-
variant: {
|
|
17
|
-
default: "text-primary",
|
|
18
|
-
muted: "text-muted-foreground",
|
|
19
|
-
white: "text-white",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
defaultVariants: {
|
|
23
|
-
size: "default",
|
|
24
|
-
variant: "default",
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
)
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
28
6
|
|
|
29
|
-
|
|
7
|
+
const spinnerVariants = cva("animate-spin", {
|
|
8
|
+
variants: {
|
|
9
|
+
size: {
|
|
10
|
+
xs: "h-3 w-3",
|
|
11
|
+
sm: "h-4 w-4",
|
|
12
|
+
default: "h-6 w-6",
|
|
13
|
+
lg: "h-8 w-8",
|
|
14
|
+
xl: "h-12 w-12",
|
|
15
|
+
},
|
|
16
|
+
variant: {
|
|
17
|
+
default: "text-primary",
|
|
18
|
+
muted: "text-muted-foreground",
|
|
19
|
+
white: "text-white",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
size: "default",
|
|
24
|
+
variant: "default",
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type SpinnerVariants = VariantProps<typeof spinnerVariants>;
|
|
30
29
|
|
|
31
30
|
interface LoadingSpinnerProps extends SpinnerVariants {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
className?: string;
|
|
32
|
+
/** Accessible label for screen readers */
|
|
33
|
+
label?: string;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* Loading spinner with size and color variants
|
|
39
|
-
*
|
|
38
|
+
*
|
|
40
39
|
* @example
|
|
41
40
|
* <LoadingSpinner />
|
|
42
41
|
* <LoadingSpinner size="lg" variant="muted" />
|
|
43
42
|
* <LoadingSpinner size="sm" label="Submitting..." />
|
|
44
43
|
*/
|
|
45
44
|
const LoadingSpinner = React.forwardRef<SVGSVGElement, LoadingSpinnerProps>(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
LoadingSpinner.displayName = "LoadingSpinner"
|
|
45
|
+
({ className, size, variant, label = "Loading" }, ref) => (
|
|
46
|
+
<svg
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(spinnerVariants({ size, variant, className }))}
|
|
49
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
50
|
+
fill="none"
|
|
51
|
+
viewBox="0 0 24 24"
|
|
52
|
+
role="status"
|
|
53
|
+
aria-label={label}
|
|
54
|
+
>
|
|
55
|
+
<circle
|
|
56
|
+
className="opacity-25"
|
|
57
|
+
cx="12"
|
|
58
|
+
cy="12"
|
|
59
|
+
r="10"
|
|
60
|
+
stroke="currentColor"
|
|
61
|
+
strokeWidth="4"
|
|
62
|
+
/>
|
|
63
|
+
<path
|
|
64
|
+
className="opacity-75"
|
|
65
|
+
fill="currentColor"
|
|
66
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
LoadingSpinner.displayName = "LoadingSpinner";
|
|
73
72
|
|
|
74
73
|
interface LoadingOverlayProps extends SpinnerVariants {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
className?: string;
|
|
75
|
+
children?: React.ReactNode;
|
|
76
|
+
/** Whether to show the loading state */
|
|
77
|
+
loading?: boolean;
|
|
78
|
+
/** Text to display below spinner */
|
|
79
|
+
text?: string;
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
83
|
* Full overlay loading state
|
|
85
|
-
*
|
|
84
|
+
*
|
|
86
85
|
* @example
|
|
87
86
|
* <LoadingOverlay loading={isLoading}>
|
|
88
87
|
* <YourContent />
|
|
89
88
|
* </LoadingOverlay>
|
|
90
89
|
*/
|
|
91
90
|
const LoadingOverlay = React.forwardRef<HTMLDivElement, LoadingOverlayProps>(
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
(
|
|
92
|
+
{ className, children, loading = true, text, size = "lg", variant },
|
|
93
|
+
ref,
|
|
94
|
+
) => {
|
|
95
|
+
if (!loading) return <>{children}</>;
|
|
94
96
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
)
|
|
106
|
-
|
|
97
|
+
return (
|
|
98
|
+
<div ref={ref} className={cn("relative", className)}>
|
|
99
|
+
{children && (
|
|
100
|
+
<div className="opacity-50 pointer-events-none">{children}</div>
|
|
101
|
+
)}
|
|
102
|
+
<div className="absolute inset-0 flex flex-col items-center justify-center bg-background/80">
|
|
103
|
+
<LoadingSpinner size={size} variant={variant} />
|
|
104
|
+
{text && <p className="mt-2 text-sm text-muted-foreground">{text}</p>}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
LoadingOverlay.displayName = "LoadingOverlay";
|
|
107
111
|
|
|
108
|
-
export { LoadingSpinner, LoadingOverlay, spinnerVariants }
|
|
112
|
+
export { LoadingSpinner, LoadingOverlay, spinnerVariants };
|
|
@@ -1,55 +1,53 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
1
|
"use client";
|
|
4
2
|
|
|
5
3
|
import { cn } from "@/lib/utils";
|
|
6
4
|
|
|
7
5
|
interface MarqueeProps {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
className?: string;
|
|
7
|
+
reverse?: boolean;
|
|
8
|
+
pauseOnHover?: boolean;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
vertical?: boolean;
|
|
11
|
+
repeat?: number;
|
|
12
|
+
[key: string]: any;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
export default function Marquee({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
className,
|
|
17
|
+
reverse,
|
|
18
|
+
pauseOnHover = false,
|
|
19
|
+
children,
|
|
20
|
+
vertical = false,
|
|
21
|
+
repeat = 4,
|
|
22
|
+
...props
|
|
25
23
|
}: MarqueeProps) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
{...props}
|
|
27
|
+
className={cn(
|
|
28
|
+
"group flex overflow-hidden p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]",
|
|
29
|
+
{
|
|
30
|
+
"flex-row": !vertical,
|
|
31
|
+
"flex-col": vertical,
|
|
32
|
+
},
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
>
|
|
36
|
+
{Array(repeat)
|
|
37
|
+
.fill(0)
|
|
38
|
+
.map((_, i) => (
|
|
39
|
+
<div
|
|
40
|
+
key={i}
|
|
41
|
+
className={cn("flex shrink-0 justify-around [gap:var(--gap)]", {
|
|
42
|
+
"animate-marquee flex-row": !vertical,
|
|
43
|
+
"animate-marquee-vertical flex-col": vertical,
|
|
44
|
+
"group-hover:[animation-play-state:paused]": pauseOnHover,
|
|
45
|
+
"[animation-direction:reverse]": reverse,
|
|
46
|
+
})}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</div>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
55
53
|
}
|
|
@@ -1,49 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
import { cn } from "@/lib/utils"
|
|
1
|
+
"use client";
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
export interface NativeSelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {}
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* NativeSelect - Styled browser-native select element
|
|
8
|
-
*
|
|
10
|
+
*
|
|
9
11
|
* Uses the browser's native <select> for accessibility and mobile UX,
|
|
10
12
|
* with custom styling to match the design system.
|
|
11
13
|
*/
|
|
12
14
|
const NativeSelect = React.forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
NativeSelect.displayName = "NativeSelect"
|
|
15
|
+
({ className, children, ...props }, ref) => {
|
|
16
|
+
return (
|
|
17
|
+
<div className="relative">
|
|
18
|
+
<select
|
|
19
|
+
ref={ref}
|
|
20
|
+
className={cn(
|
|
21
|
+
"flex h-10 w-full appearance-none rounded-md border border-input bg-background px-3 py-2 pr-8 text-sm ring-offset-background",
|
|
22
|
+
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
23
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</select>
|
|
30
|
+
{/* Custom chevron icon */}
|
|
31
|
+
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
|
32
|
+
<svg
|
|
33
|
+
className="h-4 w-4 opacity-50"
|
|
34
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
35
|
+
viewBox="0 0 24 24"
|
|
36
|
+
fill="none"
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
strokeWidth="2"
|
|
39
|
+
strokeLinecap="round"
|
|
40
|
+
strokeLinejoin="round"
|
|
41
|
+
>
|
|
42
|
+
<path d="m6 9 6 6 6-6" />
|
|
43
|
+
</svg>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
NativeSelect.displayName = "NativeSelect";
|
|
48
50
|
|
|
49
|
-
export { NativeSelect }
|
|
51
|
+
export { NativeSelect };
|