@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.
Files changed (72) hide show
  1. package/package.json +72 -0
  2. package/src/accordion.tsx +68 -0
  3. package/src/alert.tsx +107 -0
  4. package/src/avatar-group.tsx +96 -0
  5. package/src/avatar-tag.tsx +136 -0
  6. package/src/avatar.tsx +39 -0
  7. package/src/badge.tsx +98 -0
  8. package/src/blanket.tsx +61 -0
  9. package/src/breadcrumb.tsx +119 -0
  10. package/src/button-group.tsx +218 -0
  11. package/src/button.tsx +83 -0
  12. package/src/calendar.tsx +227 -0
  13. package/src/callout.tsx +68 -0
  14. package/src/card.tsx +103 -0
  15. package/src/chart-templates.tsx +587 -0
  16. package/src/chart.tsx +379 -0
  17. package/src/checkbox-select.tsx +239 -0
  18. package/src/checkbox.tsx +54 -0
  19. package/src/code.tsx +154 -0
  20. package/src/command.tsx +77 -0
  21. package/src/country-select.tsx +242 -0
  22. package/src/currency-amount-input.tsx +72 -0
  23. package/src/data-table.tsx +378 -0
  24. package/src/date-picker.tsx +317 -0
  25. package/src/dialog.tsx +81 -0
  26. package/src/drawer.tsx +91 -0
  27. package/src/dropdown-menu.tsx +174 -0
  28. package/src/empty-state.tsx +32 -0
  29. package/src/field.tsx +243 -0
  30. package/src/flag.tsx +265 -0
  31. package/src/form.tsx +168 -0
  32. package/src/grid-flex.tsx +241 -0
  33. package/src/heading.tsx +202 -0
  34. package/src/icon-button.tsx +93 -0
  35. package/src/index.ts +332 -0
  36. package/src/inline-dialog.tsx +153 -0
  37. package/src/inline-edit.tsx +212 -0
  38. package/src/input-group.tsx +151 -0
  39. package/src/input.tsx +28 -0
  40. package/src/label.tsx +21 -0
  41. package/src/layout.tsx +119 -0
  42. package/src/link.tsx +80 -0
  43. package/src/lozenge.tsx +61 -0
  44. package/src/menu.tsx +146 -0
  45. package/src/otp-input.tsx +117 -0
  46. package/src/page-header.tsx +28 -0
  47. package/src/pagination.tsx +185 -0
  48. package/src/password-input.tsx +34 -0
  49. package/src/popover.tsx +31 -0
  50. package/src/progress-indicator.tsx +94 -0
  51. package/src/progress.tsx +95 -0
  52. package/src/radio-group.tsx +46 -0
  53. package/src/responsive.tsx +276 -0
  54. package/src/scroll-area.tsx +39 -0
  55. package/src/section-message.tsx +119 -0
  56. package/src/select.tsx +144 -0
  57. package/src/separator.tsx +26 -0
  58. package/src/side-nav.tsx +264 -0
  59. package/src/skeleton.tsx +74 -0
  60. package/src/slider.tsx +25 -0
  61. package/src/sonner.tsx +32 -0
  62. package/src/spinner.tsx +54 -0
  63. package/src/spotlight.tsx +141 -0
  64. package/src/status-badge.tsx +86 -0
  65. package/src/switch.tsx +70 -0
  66. package/src/tabs.tsx +57 -0
  67. package/src/tag.tsx +52 -0
  68. package/src/textarea.tsx +25 -0
  69. package/src/time-picker.tsx +443 -0
  70. package/src/tooltip.tsx +29 -0
  71. package/src/utils.ts +6 -0
  72. 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 };
@@ -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 };