@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
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@payglocal_ui/flux-ui",
3
+ "version": "0.1.0",
4
+ "description": "Flux UI primitives — inputs, fields, dialog, data table, charts, calendar, and more (Tailwind v4 + Radix).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./src/index.ts",
9
+ "types": "./src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./src/index.ts",
13
+ "default": "./src/index.ts"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "src",
19
+ "README.md"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "keywords": [
25
+ "react",
26
+ "components",
27
+ "tailwind",
28
+ "radix-ui",
29
+ "design-system",
30
+ "flux"
31
+ ],
32
+ "peerDependencies": {
33
+ "react": "^18.0.0 || ^19.0.0",
34
+ "react-dom": "^18.0.0 || ^19.0.0"
35
+ },
36
+ "dependencies": {
37
+ "@radix-ui/react-accordion": "^1.2.13",
38
+ "@radix-ui/react-avatar": "^1.1.11",
39
+ "@radix-ui/react-checkbox": "^1.3.4",
40
+ "@radix-ui/react-dialog": "^1.1.15",
41
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
42
+ "@radix-ui/react-label": "^2.1.8",
43
+ "@radix-ui/react-popover": "^1.1.15",
44
+ "@radix-ui/react-progress": "^1.1.9",
45
+ "@radix-ui/react-radio-group": "^1.4.0",
46
+ "@radix-ui/react-scroll-area": "^1.2.10",
47
+ "@radix-ui/react-select": "^2.2.6",
48
+ "@radix-ui/react-separator": "^1.1.8",
49
+ "@radix-ui/react-slider": "^1.3.5",
50
+ "@radix-ui/react-switch": "^1.3.0",
51
+ "@radix-ui/react-tabs": "^1.1.13",
52
+ "@radix-ui/react-tooltip": "^1.2.8",
53
+ "class-variance-authority": "^0.7.1",
54
+ "clsx": "^2.1.1",
55
+ "date-fns": "^4.1.0",
56
+ "framer-motion": "^12.37.0",
57
+ "lucide-react": "^0.577.0",
58
+ "next-themes": "^0.4.6",
59
+ "react-day-picker": "^9.14.0",
60
+ "recharts": "^3.8.0",
61
+ "sonner": "^2.0.7",
62
+ "tailwind-merge": "^3.5.0"
63
+ },
64
+ "devDependencies": {
65
+ "@types/react": "^19",
66
+ "@types/react-dom": "^19",
67
+ "typescript": "^5"
68
+ },
69
+ "scripts": {
70
+ "check-types": "tsc --noEmit"
71
+ }
72
+ }
@@ -0,0 +1,68 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion";
5
+ import { ChevronDown } from "lucide-react";
6
+ import { cn } from "./utils";
7
+
8
+ const Accordion = AccordionPrimitive.Root;
9
+
10
+ const AccordionItem = React.forwardRef<
11
+ React.ElementRef<typeof AccordionPrimitive.Item>,
12
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
13
+ >(({ className, ...props }, ref) => (
14
+ <AccordionPrimitive.Item
15
+ ref={ref}
16
+ className={cn("border-b border-border last:border-b-0", className)}
17
+ {...props}
18
+ />
19
+ ));
20
+ AccordionItem.displayName = "AccordionItem";
21
+
22
+ const AccordionTrigger = React.forwardRef<
23
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
24
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
25
+ >(({ className, children, ...props }, ref) => (
26
+ <AccordionPrimitive.Header className="flex">
27
+ <AccordionPrimitive.Trigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex w-full items-center justify-between py-4 text-sm font-medium text-foreground",
31
+ "hover:text-primary transition-colors duration-pg-fast ease-pg-standard",
32
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35",
33
+ "[&[data-state=open]>svg]:rotate-180",
34
+ className
35
+ )}
36
+ {...props}
37
+ >
38
+ {children}
39
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-pg-fast ease-pg-standard" />
40
+ </AccordionPrimitive.Trigger>
41
+ </AccordionPrimitive.Header>
42
+ ));
43
+ AccordionTrigger.displayName = "AccordionTrigger";
44
+
45
+ const AccordionContent = React.forwardRef<
46
+ React.ElementRef<typeof AccordionPrimitive.Content>,
47
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
48
+ >(({ className, children, ...props }, ref) => (
49
+ <AccordionPrimitive.Content
50
+ ref={ref}
51
+ className={cn(
52
+ "overflow-hidden text-sm text-muted-foreground",
53
+ "data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up",
54
+ className
55
+ )}
56
+ style={
57
+ {
58
+ "--accordion-content-height": "var(--radix-accordion-content-height)",
59
+ } as React.CSSProperties
60
+ }
61
+ {...props}
62
+ >
63
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
64
+ </AccordionPrimitive.Content>
65
+ ));
66
+ AccordionContent.displayName = "AccordionContent";
67
+
68
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
package/src/alert.tsx ADDED
@@ -0,0 +1,107 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { X, Info, CheckCircle2, AlertTriangle, XCircle } from "lucide-react";
5
+ import { cn } from "./utils";
6
+
7
+ export type AlertVariant = "info" | "success" | "warning" | "error" | "neutral";
8
+
9
+ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ variant?: AlertVariant;
11
+ dismissible?: boolean;
12
+ onDismiss?: () => void;
13
+ }
14
+
15
+ const variantStyles: Record<AlertVariant, string> = {
16
+ info: "bg-blue-50 border-blue-200 text-blue-900 dark:bg-blue-950/30 dark:border-blue-800 dark:text-blue-200",
17
+ success: "bg-green-50 border-green-200 text-green-900 dark:bg-green-950/30 dark:border-green-800 dark:text-green-200",
18
+ warning: "bg-amber-50 border-amber-200 text-amber-900 dark:bg-amber-950/30 dark:border-amber-800 dark:text-amber-200",
19
+ error: "bg-red-50 border-red-200 text-red-900 dark:bg-red-950/30 dark:border-red-800 dark:text-red-200",
20
+ neutral: "bg-muted border-border text-foreground",
21
+ };
22
+
23
+ const variantIcons: Record<AlertVariant, React.ElementType> = {
24
+ info: Info,
25
+ success: CheckCircle2,
26
+ warning: AlertTriangle,
27
+ error: XCircle,
28
+ neutral: Info,
29
+ };
30
+
31
+ const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
32
+ ({ className, variant = "info", dismissible, onDismiss, children, ...props }, ref) => {
33
+ const Icon = variantIcons[variant];
34
+ return (
35
+ <div
36
+ ref={ref}
37
+ role="alert"
38
+ className={cn("relative flex gap-3 rounded-xl border p-4 shadow-sm", variantStyles[variant], className)}
39
+ {...props}
40
+ >
41
+ <Icon className="mt-0.5 size-4 shrink-0" aria-hidden />
42
+ <div className="flex-1 min-w-0">{children}</div>
43
+ {dismissible && (
44
+ <button
45
+ type="button"
46
+ onClick={onDismiss}
47
+ className="shrink-0 rounded-md p-0.5 opacity-60 hover:opacity-100 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35"
48
+ aria-label="Dismiss"
49
+ >
50
+ <X className="size-4" />
51
+ </button>
52
+ )}
53
+ </div>
54
+ );
55
+ }
56
+ );
57
+ Alert.displayName = "Alert";
58
+
59
+ const AlertTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
60
+ ({ className, ...props }, ref) => (
61
+ <h5 ref={ref} className={cn("mb-1 text-sm font-semibold leading-none", className)} {...props} />
62
+ )
63
+ );
64
+ AlertTitle.displayName = "AlertTitle";
65
+
66
+ const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
67
+ ({ className, ...props }, ref) => (
68
+ <p ref={ref} className={cn("text-sm opacity-90 leading-relaxed", className)} {...props} />
69
+ )
70
+ );
71
+ AlertDescription.displayName = "AlertDescription";
72
+
73
+ export interface BannerProps extends React.HTMLAttributes<HTMLDivElement> {
74
+ variant?: AlertVariant;
75
+ dismissible?: boolean;
76
+ onDismiss?: () => void;
77
+ }
78
+
79
+ const Banner = React.forwardRef<HTMLDivElement, BannerProps>(
80
+ ({ className, variant = "info", dismissible, onDismiss, children, ...props }, ref) => {
81
+ const Icon = variantIcons[variant];
82
+ return (
83
+ <div
84
+ ref={ref}
85
+ role="banner"
86
+ className={cn("flex items-center gap-3 border-b px-4 py-3", variantStyles[variant], className)}
87
+ {...props}
88
+ >
89
+ <Icon className="size-4 shrink-0" aria-hidden />
90
+ <div className="flex-1 min-w-0 text-sm">{children}</div>
91
+ {dismissible && (
92
+ <button
93
+ type="button"
94
+ onClick={onDismiss}
95
+ className="shrink-0 rounded-md p-0.5 opacity-60 hover:opacity-100 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35"
96
+ aria-label="Dismiss"
97
+ >
98
+ <X className="size-4" />
99
+ </button>
100
+ )}
101
+ </div>
102
+ );
103
+ }
104
+ );
105
+ Banner.displayName = "Banner";
106
+
107
+ export { Alert, AlertTitle, AlertDescription, Banner };
@@ -0,0 +1,96 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "./utils";
5
+ import { Avatar, AvatarImage, AvatarFallback } from "./avatar";
6
+
7
+ export interface AvatarGroupItem {
8
+ src?: string;
9
+ fallback: string;
10
+ alt?: string;
11
+ }
12
+
13
+ export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
14
+ avatars: AvatarGroupItem[];
15
+ max?: number;
16
+ size?: "sm" | "md" | "lg";
17
+ }
18
+
19
+ const sizeClasses: Record<NonNullable<AvatarGroupProps["size"]>, string> = {
20
+ sm: "h-6 w-6",
21
+ md: "h-8 w-8",
22
+ lg: "h-10 w-10",
23
+ };
24
+
25
+ const fontSizeClasses: Record<NonNullable<AvatarGroupProps["size"]>, string> = {
26
+ sm: "text-[10px]",
27
+ md: "text-xs",
28
+ lg: "text-xs",
29
+ };
30
+
31
+ const overlapClasses: Record<NonNullable<AvatarGroupProps["size"]>, string> = {
32
+ sm: "-ml-2",
33
+ md: "-ml-2",
34
+ lg: "-ml-2",
35
+ };
36
+
37
+ const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
38
+ ({ avatars, max = 4, size = "md", className, ...props }, ref) => {
39
+ const visible = avatars.slice(0, max);
40
+ const overflowCount = avatars.length - max;
41
+ const totalSlots = visible.length + (overflowCount > 0 ? 1 : 0);
42
+
43
+ return (
44
+ <div
45
+ ref={ref}
46
+ className={cn("flex items-center", className)}
47
+ {...props}
48
+ >
49
+ {visible.map((avatar, index) => {
50
+ const zIndex = totalSlots - index;
51
+ return (
52
+ <Avatar
53
+ key={index}
54
+ className={cn(
55
+ sizeClasses[size],
56
+ overlapClasses[size],
57
+ "ring-2 ring-background border border-border shrink-0 transition-[margin] duration-[var(--duration-pg-fast,150ms)] ease-[var(--ease-pg-standard,cubic-bezier(0.4,0,0.2,1))] first:ml-0",
58
+ )}
59
+ style={{ zIndex }}
60
+ >
61
+ {avatar.src && (
62
+ <AvatarImage src={avatar.src} alt={avatar.alt ?? avatar.fallback} />
63
+ )}
64
+ <AvatarFallback
65
+ className={cn(
66
+ fontSizeClasses[size],
67
+ "font-medium",
68
+ )}
69
+ >
70
+ {avatar.fallback}
71
+ </AvatarFallback>
72
+ </Avatar>
73
+ );
74
+ })}
75
+
76
+ {overflowCount > 0 && (
77
+ <div
78
+ className={cn(
79
+ sizeClasses[size],
80
+ overlapClasses[size],
81
+ fontSizeClasses[size],
82
+ "relative shrink-0 flex items-center justify-center rounded-full border border-border bg-muted text-muted-foreground font-medium ring-2 ring-background transition-[margin] duration-[var(--duration-pg-fast,150ms)] ease-[var(--ease-pg-standard,cubic-bezier(0.4,0,0.2,1))]",
83
+ )}
84
+ style={{ zIndex: 0 }}
85
+ >
86
+ +{overflowCount}
87
+ </div>
88
+ )}
89
+ </div>
90
+ );
91
+ }
92
+ );
93
+
94
+ AvatarGroup.displayName = "AvatarGroup";
95
+
96
+ export { AvatarGroup };
@@ -0,0 +1,136 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "./utils";
7
+
8
+ export type AvatarTagSize = "sm" | "md" | "lg";
9
+
10
+ export interface AvatarTagProps extends React.HTMLAttributes<HTMLSpanElement> {
11
+ /** Display label for the tag */
12
+ label: string;
13
+ /** Optional image src for the avatar */
14
+ src?: string;
15
+ /** Alt text for the avatar image */
16
+ alt?: string;
17
+ /** Size variant */
18
+ size?: AvatarTagSize;
19
+ /** Called when the remove button is clicked */
20
+ onRemove?: () => void;
21
+ /** Disables interaction */
22
+ disabled?: boolean;
23
+ }
24
+
25
+ function getInitials(label: string): string {
26
+ const words = label.trim().split(/\s+/);
27
+ if (words.length === 1) {
28
+ return words[0].slice(0, 2).toUpperCase();
29
+ }
30
+ return (words[0][0] + words[words.length - 1][0]).toUpperCase();
31
+ }
32
+
33
+ const sizeConfig: Record<
34
+ AvatarTagSize,
35
+ { avatar: string; text: string; icon: string; padding: string }
36
+ > = {
37
+ sm: {
38
+ avatar: "h-[18px] w-[18px]",
39
+ text: "text-[10px]",
40
+ icon: "size-2.5",
41
+ padding: "px-1.5 py-0.5 gap-1",
42
+ },
43
+ md: {
44
+ avatar: "h-5 w-5",
45
+ text: "text-xs",
46
+ icon: "size-3",
47
+ padding: "px-2 py-0.5 gap-1",
48
+ },
49
+ lg: {
50
+ avatar: "h-6 w-6",
51
+ text: "text-sm",
52
+ icon: "size-3.5",
53
+ padding: "px-2.5 py-1 gap-1.5",
54
+ },
55
+ };
56
+
57
+ const AvatarTag = React.forwardRef<HTMLSpanElement, AvatarTagProps>(
58
+ (
59
+ {
60
+ label,
61
+ src,
62
+ alt,
63
+ size = "md",
64
+ onRemove,
65
+ disabled = false,
66
+ className,
67
+ ...props
68
+ },
69
+ ref
70
+ ) => {
71
+ const cfg = sizeConfig[size];
72
+ const initials = getInitials(label);
73
+
74
+ return (
75
+ <span
76
+ ref={ref}
77
+ className={cn(
78
+ "inline-flex items-center rounded-full border border-border bg-card font-medium transition-colors duration-[var(--duration-pg-fast,150ms)] ease-[var(--ease-pg-standard,cubic-bezier(0.4,0,0.2,1))]",
79
+ cfg.padding,
80
+ cfg.text,
81
+ disabled && "opacity-50 cursor-not-allowed",
82
+ className
83
+ )}
84
+ {...props}
85
+ >
86
+ {/* Avatar */}
87
+ <AvatarPrimitive.Root
88
+ className={cn(
89
+ "relative shrink-0 overflow-hidden rounded-full",
90
+ cfg.avatar
91
+ )}
92
+ >
93
+ <AvatarPrimitive.Image
94
+ src={src}
95
+ alt={alt ?? label}
96
+ className="aspect-square h-full w-full object-cover"
97
+ />
98
+ <AvatarPrimitive.Fallback
99
+ className={cn(
100
+ "flex h-full w-full items-center justify-center rounded-full bg-primary/10 text-primary font-semibold leading-none",
101
+ cfg.text
102
+ )}
103
+ >
104
+ {initials}
105
+ </AvatarPrimitive.Fallback>
106
+ </AvatarPrimitive.Root>
107
+
108
+ {/* Label */}
109
+ <span className="leading-none select-none">{label}</span>
110
+
111
+ {/* Remove button */}
112
+ {onRemove && (
113
+ <button
114
+ type="button"
115
+ onClick={(e) => {
116
+ e.stopPropagation();
117
+ if (!disabled) onRemove();
118
+ }}
119
+ disabled={disabled}
120
+ className={cn(
121
+ "flex items-center justify-center rounded-full opacity-60 transition-opacity duration-[var(--duration-pg-fast,150ms)] hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35",
122
+ disabled && "pointer-events-none"
123
+ )}
124
+ aria-label={`Remove ${label}`}
125
+ >
126
+ <X className={cfg.icon} />
127
+ </button>
128
+ )}
129
+ </span>
130
+ );
131
+ }
132
+ );
133
+
134
+ AvatarTag.displayName = "AvatarTag";
135
+
136
+ export { AvatarTag };
package/src/avatar.tsx ADDED
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
5
+ import { cn } from "./utils";
6
+
7
+ const Avatar = React.forwardRef<
8
+ React.ElementRef<typeof AvatarPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <AvatarPrimitive.Root
12
+ ref={ref}
13
+ className={cn("relative flex h-9 w-9 shrink-0 overflow-hidden rounded-full border border-border bg-muted", className)}
14
+ {...props}
15
+ />
16
+ ));
17
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
18
+
19
+ const AvatarImage = React.forwardRef<
20
+ React.ElementRef<typeof AvatarPrimitive.Image>,
21
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
22
+ >(({ className, ...props }, ref) => (
23
+ <AvatarPrimitive.Image ref={ref} className={cn("aspect-square h-full w-full object-cover", className)} {...props} />
24
+ ));
25
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
26
+
27
+ const AvatarFallback = React.forwardRef<
28
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
29
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
30
+ >(({ className, ...props }, ref) => (
31
+ <AvatarPrimitive.Fallback
32
+ ref={ref}
33
+ className={cn("flex h-full w-full items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground", className)}
34
+ {...props}
35
+ />
36
+ ));
37
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
38
+
39
+ export { Avatar, AvatarImage, AvatarFallback };
package/src/badge.tsx ADDED
@@ -0,0 +1,98 @@
1
+ "use client";
2
+
3
+ import { cn } from "./utils";
4
+ import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
5
+
6
+ export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
7
+ variant?: "default" | "secondary" | "success" | "warning" | "error" | "outline";
8
+ size?: "sm" | "md" | "lg";
9
+ square?: boolean;
10
+ dot?: boolean;
11
+ leftIcon?: ReactNode;
12
+ rightIcon?: ReactNode;
13
+ }
14
+
15
+ const variantClasses: Record<NonNullable<BadgeProps["variant"]>, string> = {
16
+ default:
17
+ "bg-primary/10 text-primary border border-primary/20",
18
+ secondary:
19
+ "bg-muted text-muted-foreground border border-border",
20
+ success:
21
+ "bg-green-500/10 text-green-700 border border-green-500/20 dark:text-green-400",
22
+ warning:
23
+ "bg-amber-500/10 text-amber-700 border border-amber-500/20 dark:text-amber-400",
24
+ error:
25
+ "bg-red-500/10 text-red-700 border border-red-500/20 dark:text-red-400",
26
+ outline:
27
+ "bg-transparent text-foreground border border-border",
28
+ };
29
+
30
+ const sizeClasses: Record<NonNullable<BadgeProps["size"]>, string> = {
31
+ sm: "text-[10px] px-1.5 py-0.5 gap-1",
32
+ md: "text-xs px-2 py-0.5 gap-1",
33
+ lg: "text-sm px-2.5 py-1 gap-1.5",
34
+ };
35
+
36
+ const dotColorClasses: Record<NonNullable<BadgeProps["variant"]>, string> = {
37
+ default: "bg-primary",
38
+ secondary: "bg-muted-foreground",
39
+ success: "bg-green-500",
40
+ warning: "bg-amber-500",
41
+ error: "bg-red-500",
42
+ outline: "bg-foreground",
43
+ };
44
+
45
+ const dotSizeClasses: Record<NonNullable<BadgeProps["size"]>, string> = {
46
+ sm: "size-1.5",
47
+ md: "size-1.5",
48
+ lg: "size-2",
49
+ };
50
+
51
+ export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
52
+ (
53
+ {
54
+ variant = "default",
55
+ size = "md",
56
+ square = false,
57
+ dot = false,
58
+ leftIcon,
59
+ rightIcon,
60
+ children,
61
+ className,
62
+ ...props
63
+ },
64
+ ref
65
+ ) => {
66
+ return (
67
+ <span
68
+ ref={ref}
69
+ className={cn(
70
+ "inline-flex items-center font-medium whitespace-nowrap transition-colors duration-pg-fast ease-pg-standard",
71
+ square ? "rounded-md" : "rounded-full",
72
+ variantClasses[variant],
73
+ sizeClasses[size],
74
+ className
75
+ )}
76
+ {...props}
77
+ >
78
+ {dot && (
79
+ <span
80
+ className={cn(
81
+ "rounded-full flex-shrink-0",
82
+ dotSizeClasses[size],
83
+ dotColorClasses[variant]
84
+ )}
85
+ />
86
+ )}
87
+ {!dot && leftIcon && (
88
+ <span className="inline-flex items-center flex-shrink-0">{leftIcon}</span>
89
+ )}
90
+ {children}
91
+ {rightIcon && (
92
+ <span className="inline-flex items-center flex-shrink-0">{rightIcon}</span>
93
+ )}
94
+ </span>
95
+ );
96
+ }
97
+ );
98
+ Badge.displayName = "Badge";
@@ -0,0 +1,61 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "./utils";
5
+
6
+ export interface BlanketProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ /** Controls visibility of the blanket */
8
+ open?: boolean;
9
+ /** Adds semi-transparent black background (default: true) */
10
+ isTinted?: boolean;
11
+ /** Makes blanket completely transparent — only blocks pointer events */
12
+ isTransparent?: boolean;
13
+ /** Called when the blanket is clicked */
14
+ onClick?: React.MouseEventHandler<HTMLDivElement>;
15
+ /** When true, pointer events pass through the blanket */
16
+ shouldAllowClickThrough?: boolean;
17
+ }
18
+
19
+ const Blanket = React.forwardRef<HTMLDivElement, BlanketProps>(
20
+ (
21
+ {
22
+ open = true,
23
+ isTinted = true,
24
+ isTransparent = false,
25
+ onClick,
26
+ shouldAllowClickThrough = false,
27
+ className,
28
+ ...props
29
+ },
30
+ ref
31
+ ) => {
32
+ return (
33
+ <div
34
+ ref={ref}
35
+ data-open={open}
36
+ aria-hidden="true"
37
+ onClick={onClick}
38
+ className={cn(
39
+ "fixed inset-0 z-40",
40
+ // Animate in/out
41
+ "data-[open=true]:animate-in data-[open=false]:animate-out",
42
+ "data-[open=true]:fade-in-0 data-[open=false]:fade-out-0",
43
+ "transition-colors duration-pg-fast ease-pg-standard",
44
+ // Visibility
45
+ !open && "pointer-events-none",
46
+ // Background
47
+ !isTransparent && isTinted && "bg-black/50 backdrop-blur-sm",
48
+ isTransparent && "bg-transparent",
49
+ // Click-through
50
+ shouldAllowClickThrough && "pointer-events-none",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+ );
58
+
59
+ Blanket.displayName = "Blanket";
60
+
61
+ export { Blanket };