@payglocal_ui/flux-ui 0.1.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.
- package/package.json +72 -0
- package/src/accordion.tsx +68 -0
- package/src/alert.tsx +107 -0
- package/src/avatar-group.tsx +96 -0
- package/src/avatar-tag.tsx +136 -0
- package/src/avatar.tsx +39 -0
- package/src/badge.tsx +98 -0
- package/src/blanket.tsx +61 -0
- package/src/breadcrumb.tsx +119 -0
- package/src/button-group.tsx +218 -0
- package/src/button.tsx +83 -0
- package/src/calendar.tsx +227 -0
- package/src/callout.tsx +68 -0
- package/src/card.tsx +103 -0
- package/src/chart-templates.tsx +587 -0
- package/src/chart.tsx +379 -0
- package/src/checkbox-select.tsx +239 -0
- package/src/checkbox.tsx +54 -0
- package/src/code.tsx +154 -0
- package/src/command.tsx +77 -0
- package/src/country-select.tsx +242 -0
- package/src/currency-amount-input.tsx +72 -0
- package/src/data-table.tsx +378 -0
- package/src/date-picker.tsx +317 -0
- package/src/dialog.tsx +81 -0
- package/src/drawer.tsx +91 -0
- package/src/dropdown-menu.tsx +174 -0
- package/src/empty-state.tsx +32 -0
- package/src/field.tsx +243 -0
- package/src/flag.tsx +265 -0
- package/src/form.tsx +168 -0
- package/src/grid-flex.tsx +241 -0
- package/src/heading.tsx +202 -0
- package/src/icon-button.tsx +93 -0
- package/src/index.ts +332 -0
- package/src/inline-dialog.tsx +153 -0
- package/src/inline-edit.tsx +212 -0
- package/src/input-group.tsx +151 -0
- package/src/input.tsx +28 -0
- package/src/label.tsx +21 -0
- package/src/layout.tsx +119 -0
- package/src/link.tsx +80 -0
- package/src/lozenge.tsx +61 -0
- package/src/menu.tsx +146 -0
- package/src/otp-input.tsx +117 -0
- package/src/page-header.tsx +28 -0
- package/src/pagination.tsx +185 -0
- package/src/password-input.tsx +34 -0
- package/src/popover.tsx +31 -0
- package/src/progress-indicator.tsx +94 -0
- package/src/progress.tsx +95 -0
- package/src/radio-group.tsx +46 -0
- package/src/responsive.tsx +276 -0
- package/src/scroll-area.tsx +39 -0
- package/src/section-message.tsx +119 -0
- package/src/select.tsx +144 -0
- package/src/separator.tsx +26 -0
- package/src/side-nav.tsx +264 -0
- package/src/skeleton.tsx +74 -0
- package/src/slider.tsx +25 -0
- package/src/sonner.tsx +32 -0
- package/src/spinner.tsx +54 -0
- package/src/spotlight.tsx +141 -0
- package/src/status-badge.tsx +86 -0
- package/src/switch.tsx +70 -0
- package/src/tabs.tsx +57 -0
- package/src/tag.tsx +52 -0
- package/src/textarea.tsx +25 -0
- package/src/time-picker.tsx +443 -0
- package/src/tooltip.tsx +29 -0
- package/src/utils.ts +6 -0
- package/src/visually-hidden.tsx +25 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { X, ChevronLeft, ChevronRight, Check } from "lucide-react";
|
|
5
|
+
import { cn } from "./utils";
|
|
6
|
+
|
|
7
|
+
export interface SpotlightStep {
|
|
8
|
+
title: string;
|
|
9
|
+
body: string;
|
|
10
|
+
image?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SpotlightCardProps {
|
|
14
|
+
title: string;
|
|
15
|
+
body: string;
|
|
16
|
+
image?: React.ReactNode;
|
|
17
|
+
currentStep?: number;
|
|
18
|
+
totalSteps?: number;
|
|
19
|
+
onNext?: () => void;
|
|
20
|
+
onBack?: () => void;
|
|
21
|
+
onDismiss?: () => void;
|
|
22
|
+
nextLabel?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SpotlightCard = React.forwardRef<HTMLDivElement, SpotlightCardProps>(
|
|
27
|
+
({ title, body, image, currentStep, totalSteps, onNext, onBack, onDismiss, nextLabel, className }, ref) => {
|
|
28
|
+
const isLast = currentStep !== undefined && totalSteps !== undefined && currentStep >= totalSteps - 1;
|
|
29
|
+
return (
|
|
30
|
+
<div ref={ref} className={cn("bg-card border border-border rounded-xl shadow-xl p-5 w-72", className)}>
|
|
31
|
+
{onDismiss && (
|
|
32
|
+
<button
|
|
33
|
+
onClick={onDismiss}
|
|
34
|
+
className="absolute top-3 right-3 rounded-md p-1 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35"
|
|
35
|
+
aria-label="Dismiss"
|
|
36
|
+
>
|
|
37
|
+
<X className="size-4" />
|
|
38
|
+
</button>
|
|
39
|
+
)}
|
|
40
|
+
{image && <div className="mb-4">{image}</div>}
|
|
41
|
+
<h3 className="font-semibold text-base text-foreground pr-6">{title}</h3>
|
|
42
|
+
<p className="mt-1 text-sm text-muted-foreground leading-relaxed">{body}</p>
|
|
43
|
+
<div className="flex items-center justify-between mt-4">
|
|
44
|
+
<div className="flex items-center gap-2">
|
|
45
|
+
{totalSteps && totalSteps > 1 && (
|
|
46
|
+
<>
|
|
47
|
+
<span className="text-xs text-muted-foreground">{(currentStep ?? 0) + 1} of {totalSteps}</span>
|
|
48
|
+
<div className="flex gap-1">
|
|
49
|
+
{Array.from({ length: totalSteps }).map((_, i) => (
|
|
50
|
+
<span
|
|
51
|
+
key={i}
|
|
52
|
+
className={cn(
|
|
53
|
+
"rounded-full transition-all duration-pg-fast",
|
|
54
|
+
i === (currentStep ?? 0)
|
|
55
|
+
? "w-4 h-1.5 bg-primary"
|
|
56
|
+
: "w-1.5 h-1.5 bg-muted-foreground/30"
|
|
57
|
+
)}
|
|
58
|
+
/>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
</>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex gap-2">
|
|
65
|
+
{onBack && (currentStep ?? 0) > 0 && (
|
|
66
|
+
<button
|
|
67
|
+
onClick={onBack}
|
|
68
|
+
className="inline-flex items-center gap-1 h-8 px-3 rounded-lg text-xs font-medium text-muted-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35"
|
|
69
|
+
>
|
|
70
|
+
<ChevronLeft className="size-3" /> Back
|
|
71
|
+
</button>
|
|
72
|
+
)}
|
|
73
|
+
{onNext && (
|
|
74
|
+
<button
|
|
75
|
+
onClick={onNext}
|
|
76
|
+
className="inline-flex items-center gap-1 h-8 px-3 rounded-lg text-xs font-medium bg-primary text-primary-foreground hover:bg-[var(--primary-hover)] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35"
|
|
77
|
+
>
|
|
78
|
+
{isLast ? (
|
|
79
|
+
<><Check className="size-3" /> {nextLabel ?? "Done"}</>
|
|
80
|
+
) : (
|
|
81
|
+
<>{nextLabel ?? "Next"} <ChevronRight className="size-3" /></>
|
|
82
|
+
)}
|
|
83
|
+
</button>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
SpotlightCard.displayName = "SpotlightCard";
|
|
92
|
+
|
|
93
|
+
export interface SpotlightProps {
|
|
94
|
+
isOpen: boolean;
|
|
95
|
+
onClose: () => void;
|
|
96
|
+
children: React.ReactNode;
|
|
97
|
+
className?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const Spotlight = ({ isOpen, onClose, children, className }: SpotlightProps) => {
|
|
101
|
+
if (!isOpen) return null;
|
|
102
|
+
return (
|
|
103
|
+
<div className="fixed inset-0 z-50">
|
|
104
|
+
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
|
|
105
|
+
<div className={cn("relative z-10 flex items-center justify-center min-h-full p-4", className)}>
|
|
106
|
+
{children}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
Spotlight.displayName = "Spotlight";
|
|
112
|
+
|
|
113
|
+
export interface UseSpotlightReturn {
|
|
114
|
+
isOpen: boolean;
|
|
115
|
+
currentStep: number;
|
|
116
|
+
open: () => void;
|
|
117
|
+
close: () => void;
|
|
118
|
+
goNext: () => void;
|
|
119
|
+
goBack: () => void;
|
|
120
|
+
goTo: (step: number) => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function useSpotlight(steps: SpotlightStep[], initialStep = 0): UseSpotlightReturn {
|
|
124
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
125
|
+
const [currentStep, setCurrentStep] = React.useState(initialStep);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
isOpen,
|
|
129
|
+
currentStep,
|
|
130
|
+
open: () => { setCurrentStep(initialStep); setIsOpen(true); },
|
|
131
|
+
close: () => setIsOpen(false),
|
|
132
|
+
goNext: () => {
|
|
133
|
+
if (currentStep >= steps.length - 1) setIsOpen(false);
|
|
134
|
+
else setCurrentStep(s => s + 1);
|
|
135
|
+
},
|
|
136
|
+
goBack: () => setCurrentStep(s => Math.max(0, s - 1)),
|
|
137
|
+
goTo: (step) => setCurrentStep(Math.max(0, Math.min(step, steps.length - 1))),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { Spotlight, SpotlightCard };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Check, X, RefreshCw, Clock, AlertCircle, ArrowRight, Info } from "lucide-react";
|
|
5
|
+
import { cn } from "./utils";
|
|
6
|
+
|
|
7
|
+
export type BadgeVariant =
|
|
8
|
+
| "success"
|
|
9
|
+
| "info"
|
|
10
|
+
| "warning"
|
|
11
|
+
| "refund"
|
|
12
|
+
| "danger"
|
|
13
|
+
| "orange"
|
|
14
|
+
| "muted";
|
|
15
|
+
|
|
16
|
+
export type BadgeTrailIcon =
|
|
17
|
+
| "check"
|
|
18
|
+
| "x"
|
|
19
|
+
| "refresh"
|
|
20
|
+
| "clock"
|
|
21
|
+
| "alert"
|
|
22
|
+
| "arrow-right"
|
|
23
|
+
| "info";
|
|
24
|
+
|
|
25
|
+
/* Light: low-opacity hue-matched borders. Dark: stronger borders for contrast on dark surfaces. */
|
|
26
|
+
const VARIANT_CLASS: Record<BadgeVariant, string> = {
|
|
27
|
+
success:
|
|
28
|
+
"bg-emerald-500/10 text-emerald-800 border-emerald-600/10 dark:bg-emerald-500/35 dark:text-emerald-50 dark:border-emerald-400/70",
|
|
29
|
+
info: "bg-blue-500/10 text-blue-800 border-blue-600/10 dark:bg-sky-500/30 dark:text-sky-50 dark:border-sky-400/65",
|
|
30
|
+
warning:
|
|
31
|
+
"bg-amber-500/10 text-amber-900 border-amber-600/10 dark:bg-amber-500/35 dark:text-amber-50 dark:border-amber-400/70",
|
|
32
|
+
refund:
|
|
33
|
+
"bg-yellow-500/10 text-yellow-900 border-yellow-600/10 dark:bg-violet-500/30 dark:text-violet-100 dark:border-violet-400/60",
|
|
34
|
+
danger:
|
|
35
|
+
"bg-red-500/10 text-red-800 border-red-600/10 dark:bg-red-500/35 dark:text-red-50 dark:border-red-400/70",
|
|
36
|
+
orange:
|
|
37
|
+
"bg-orange-500/10 text-orange-900 border-orange-600/10 dark:bg-orange-500/35 dark:text-orange-50 dark:border-orange-400/65",
|
|
38
|
+
muted:
|
|
39
|
+
"bg-muted text-muted-foreground border-border/40 dark:bg-zinc-800/90 dark:text-zinc-200 dark:border-zinc-500/45",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export interface StatusBadgeProps {
|
|
43
|
+
variant: BadgeVariant;
|
|
44
|
+
label: string;
|
|
45
|
+
trailIcon?: BadgeTrailIcon;
|
|
46
|
+
size?: "sm" | "md";
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const StatusBadge = React.forwardRef<HTMLSpanElement, StatusBadgeProps>(
|
|
51
|
+
({ variant, label, trailIcon, size = "md", className }, ref) => {
|
|
52
|
+
const iSize = size === "sm" ? 11 : 12;
|
|
53
|
+
const iProps = {
|
|
54
|
+
width: iSize,
|
|
55
|
+
height: iSize,
|
|
56
|
+
strokeWidth: 2.5,
|
|
57
|
+
style: { flexShrink: 0 },
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
const icon =
|
|
61
|
+
trailIcon === "check" ? <Check {...iProps} /> :
|
|
62
|
+
trailIcon === "x" ? <X {...iProps} strokeWidth={3} /> :
|
|
63
|
+
trailIcon === "refresh" ? <RefreshCw {...{ ...iProps, strokeWidth: 2 }} /> :
|
|
64
|
+
trailIcon === "clock" ? <Clock {...iProps} strokeWidth={2} /> :
|
|
65
|
+
trailIcon === "alert" ? <AlertCircle {...iProps} strokeWidth={2} /> :
|
|
66
|
+
trailIcon === "arrow-right" ? <ArrowRight {...iProps} strokeWidth={2.5} /> :
|
|
67
|
+
trailIcon === "info" ? <Info {...iProps} strokeWidth={2.5} /> :
|
|
68
|
+
null;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<span
|
|
72
|
+
ref={ref}
|
|
73
|
+
className={cn(
|
|
74
|
+
"inline-flex items-center gap-1.5 font-medium border whitespace-nowrap rounded-md",
|
|
75
|
+
VARIANT_CLASS[variant],
|
|
76
|
+
size === "sm" ? "text-[11px] px-2 py-[2px]" : "text-[13px] px-3 py-[5px]",
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
{label}
|
|
81
|
+
{icon}
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
StatusBadge.displayName = "StatusBadge";
|
package/src/switch.tsx
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
|
|
7
|
+
import { cn } from "./utils";
|
|
8
|
+
|
|
9
|
+
const switchRootVariants = cva(
|
|
10
|
+
[
|
|
11
|
+
"peer inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent",
|
|
12
|
+
"transition-colors duration-pg-fast ease-pg-standard",
|
|
13
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35",
|
|
14
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
15
|
+
"data-[state=unchecked]:bg-muted data-[state=unchecked]:border-border",
|
|
16
|
+
"data-[state=checked]:bg-primary data-[state=checked]:border-transparent",
|
|
17
|
+
],
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
size: {
|
|
21
|
+
sm: "h-5 w-9",
|
|
22
|
+
md: "h-6 w-11",
|
|
23
|
+
lg: "h-7 w-[3.25rem]",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
size: "md",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const switchThumbVariants = cva(
|
|
33
|
+
[
|
|
34
|
+
"pointer-events-none block rounded-full bg-white shadow-sm",
|
|
35
|
+
"transition-transform duration-pg-fast ease-pg-standard",
|
|
36
|
+
"ring-0",
|
|
37
|
+
],
|
|
38
|
+
{
|
|
39
|
+
variants: {
|
|
40
|
+
size: {
|
|
41
|
+
sm: "size-3.5 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0.5",
|
|
42
|
+
md: "size-4.5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0.5",
|
|
43
|
+
lg: "size-5.5 data-[state=checked]:translate-x-6 data-[state=unchecked]:translate-x-0.5",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultVariants: {
|
|
47
|
+
size: "md",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export interface SwitchProps
|
|
53
|
+
extends React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>,
|
|
54
|
+
VariantProps<typeof switchRootVariants> {}
|
|
55
|
+
|
|
56
|
+
const Switch = React.forwardRef<
|
|
57
|
+
React.ElementRef<typeof SwitchPrimitive.Root>,
|
|
58
|
+
SwitchProps
|
|
59
|
+
>(({ className, size, ...props }, ref) => (
|
|
60
|
+
<SwitchPrimitive.Root
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn(switchRootVariants({ size }), className)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
<SwitchPrimitive.Thumb className={cn(switchThumbVariants({ size }))} />
|
|
66
|
+
</SwitchPrimitive.Root>
|
|
67
|
+
));
|
|
68
|
+
Switch.displayName = SwitchPrimitive.Root.displayName;
|
|
69
|
+
|
|
70
|
+
export { Switch };
|
package/src/tabs.tsx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
|
+
import { cn } from "./utils";
|
|
6
|
+
|
|
7
|
+
const Tabs = TabsPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const TabsList = React.forwardRef<
|
|
10
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
11
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
12
|
+
>(({ className, ...props }, ref) => (
|
|
13
|
+
<TabsPrimitive.List
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
"inline-flex h-9 items-center justify-center gap-0.5 rounded-lg border border-border bg-muted/40 p-0.5 text-muted-foreground",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
));
|
|
22
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
23
|
+
|
|
24
|
+
const TabsTrigger = React.forwardRef<
|
|
25
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
26
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
27
|
+
>(({ className, ...props }, ref) => (
|
|
28
|
+
<TabsPrimitive.Trigger
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cn(
|
|
31
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-xs font-medium transition-colors",
|
|
32
|
+
"ring-ring/50 focus-visible:outline-none focus-visible:ring-2",
|
|
33
|
+
"data-[state=active]:bg-card data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
34
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
));
|
|
40
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
41
|
+
|
|
42
|
+
const TabsContent = React.forwardRef<
|
|
43
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
44
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
45
|
+
>(({ className, ...props }, ref) => (
|
|
46
|
+
<TabsPrimitive.Content
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(
|
|
49
|
+
"mt-3 outline-none ring-ring/50 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded-lg",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
));
|
|
55
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
56
|
+
|
|
57
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
package/src/tag.tsx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { X } from "lucide-react";
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
|
|
5
|
+
export type TagColorScheme = "neutral" | "blue" | "green" | "amber" | "red" | "purple";
|
|
6
|
+
|
|
7
|
+
export interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
8
|
+
colorScheme?: TagColorScheme;
|
|
9
|
+
onRemove?: () => void;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const colorClasses: Record<TagColorScheme, string> = {
|
|
14
|
+
neutral: "bg-card border-border text-foreground",
|
|
15
|
+
blue: "bg-blue-500/10 border-blue-500/20 text-blue-700 dark:text-blue-300",
|
|
16
|
+
green: "bg-green-500/10 border-green-500/20 text-green-700 dark:text-green-300",
|
|
17
|
+
amber: "bg-amber-500/10 border-amber-500/20 text-amber-700 dark:text-amber-300",
|
|
18
|
+
red: "bg-red-500/10 border-red-500/20 text-red-700 dark:text-red-300",
|
|
19
|
+
purple: "bg-purple-500/10 border-purple-500/20 text-purple-700 dark:text-purple-300",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
|
|
23
|
+
({ className, colorScheme = "neutral", onRemove, disabled, children, ...props }, ref) => (
|
|
24
|
+
<span
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn("inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium transition-colors", colorClasses[colorScheme], disabled && "opacity-50 cursor-not-allowed", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
{onRemove && !disabled && (
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
onClick={(e) => { e.stopPropagation(); onRemove(); }}
|
|
34
|
+
className="ml-0.5 rounded-sm opacity-60 hover:opacity-100 transition-opacity focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
35
|
+
aria-label="Remove"
|
|
36
|
+
>
|
|
37
|
+
<X className="size-3" />
|
|
38
|
+
</button>
|
|
39
|
+
)}
|
|
40
|
+
</span>
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
Tag.displayName = "Tag";
|
|
44
|
+
|
|
45
|
+
export interface TagGroupProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
46
|
+
|
|
47
|
+
const TagGroup = React.forwardRef<HTMLDivElement, TagGroupProps>(
|
|
48
|
+
({ className, ...props }, ref) => <div ref={ref} className={cn("flex flex-wrap gap-1.5", className)} {...props} />
|
|
49
|
+
);
|
|
50
|
+
TagGroup.displayName = "TagGroup";
|
|
51
|
+
|
|
52
|
+
export { Tag, TagGroup };
|
package/src/textarea.tsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
|
|
5
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
|
|
6
|
+
({ className, ...props }, ref) => {
|
|
7
|
+
return (
|
|
8
|
+
<textarea
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex min-h-[5.5rem] w-full rounded-lg border border-border bg-card px-4 py-3 text-[15px] leading-relaxed shadow-sm transition-colors",
|
|
11
|
+
"placeholder:text-muted-foreground",
|
|
12
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
13
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
14
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
ref={ref}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
Textarea.displayName = "Textarea";
|
|
24
|
+
|
|
25
|
+
export { Textarea };
|