@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/src/select.tsx ADDED
@@ -0,0 +1,144 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SelectPrimitive from "@radix-ui/react-select";
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
6
+ import { cn } from "./utils";
7
+
8
+ const Select = SelectPrimitive.Root;
9
+ const SelectGroup = SelectPrimitive.Group;
10
+ const SelectValue = SelectPrimitive.Value;
11
+
12
+ const SelectTrigger = React.forwardRef<
13
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
14
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
15
+ >(({ className, children, ...props }, ref) => (
16
+ <SelectPrimitive.Trigger
17
+ ref={ref}
18
+ className={cn(
19
+ "flex h-11 min-h-11 w-full items-center justify-between gap-2.5 rounded-lg border border-border bg-card px-4 py-2 text-[15px] text-foreground shadow-sm outline-none",
20
+ "ring-ring/50 focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
21
+ "[&>span]:line-clamp-1",
22
+ className
23
+ )}
24
+ {...props}
25
+ >
26
+ {children}
27
+ <SelectPrimitive.Icon asChild>
28
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground opacity-70" />
29
+ </SelectPrimitive.Icon>
30
+ </SelectPrimitive.Trigger>
31
+ ));
32
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
33
+
34
+ const SelectScrollUpButton = React.forwardRef<
35
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
36
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
37
+ >(({ className, ...props }, ref) => (
38
+ <SelectPrimitive.ScrollUpButton
39
+ ref={ref}
40
+ className={cn("flex cursor-default items-center justify-center py-1 text-muted-foreground", className)}
41
+ {...props}
42
+ >
43
+ <ChevronUp className="h-4 w-4" />
44
+ </SelectPrimitive.ScrollUpButton>
45
+ ));
46
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
47
+
48
+ const SelectScrollDownButton = React.forwardRef<
49
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
50
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
51
+ >(({ className, ...props }, ref) => (
52
+ <SelectPrimitive.ScrollDownButton
53
+ ref={ref}
54
+ className={cn("flex cursor-default items-center justify-center py-1 text-muted-foreground", className)}
55
+ {...props}
56
+ >
57
+ <ChevronDown className="h-4 w-4" />
58
+ </SelectPrimitive.ScrollDownButton>
59
+ ));
60
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
61
+
62
+ const SelectContent = React.forwardRef<
63
+ React.ElementRef<typeof SelectPrimitive.Content>,
64
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
65
+ >(({ className, children, position = "popper", ...props }, ref) => (
66
+ <SelectPrimitive.Portal>
67
+ <SelectPrimitive.Content
68
+ ref={ref}
69
+ className={cn(
70
+ "relative z-[120] max-h-[min(24rem,var(--radix-select-content-available-height))] min-w-[var(--radix-select-trigger-width)] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-lg",
71
+ "data-[state=open]:opacity-100 data-[state=closed]:opacity-0 transition-opacity duration-150",
72
+ position === "popper" &&
73
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
74
+ className
75
+ )}
76
+ position={position}
77
+ {...props}
78
+ >
79
+ <SelectScrollUpButton />
80
+ <SelectPrimitive.Viewport
81
+ className={cn(
82
+ "p-1 max-h-72 overflow-y-auto",
83
+ position === "popper" && "w-full min-w-[var(--radix-select-trigger-width)]"
84
+ )}
85
+ >
86
+ {children}
87
+ </SelectPrimitive.Viewport>
88
+ <SelectScrollDownButton />
89
+ </SelectPrimitive.Content>
90
+ </SelectPrimitive.Portal>
91
+ ));
92
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
93
+
94
+ const SelectLabel = React.forwardRef<
95
+ React.ElementRef<typeof SelectPrimitive.Label>,
96
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
97
+ >(({ className, ...props }, ref) => (
98
+ <SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-xs font-medium text-muted-foreground", className)} {...props} />
99
+ ));
100
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
101
+
102
+ const SelectItem = React.forwardRef<
103
+ React.ElementRef<typeof SelectPrimitive.Item>,
104
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
105
+ >(({ className, children, ...props }, ref) => (
106
+ <SelectPrimitive.Item
107
+ ref={ref}
108
+ className={cn(
109
+ "relative flex w-full cursor-default select-none items-center rounded-lg py-2 pl-2 pr-8 text-sm outline-none",
110
+ "focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
111
+ className
112
+ )}
113
+ {...props}
114
+ >
115
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
116
+ <SelectPrimitive.ItemIndicator>
117
+ <Check className="h-4 w-4 text-primary" />
118
+ </SelectPrimitive.ItemIndicator>
119
+ </span>
120
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
121
+ </SelectPrimitive.Item>
122
+ ));
123
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
124
+
125
+ const SelectSeparator = React.forwardRef<
126
+ React.ElementRef<typeof SelectPrimitive.Separator>,
127
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
128
+ >(({ className, ...props }, ref) => (
129
+ <SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} />
130
+ ));
131
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
132
+
133
+ export {
134
+ Select,
135
+ SelectGroup,
136
+ SelectValue,
137
+ SelectTrigger,
138
+ SelectContent,
139
+ SelectLabel,
140
+ SelectItem,
141
+ SelectSeparator,
142
+ SelectScrollUpButton,
143
+ SelectScrollDownButton,
144
+ };
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
+
6
+ import { cn } from "./utils";
7
+
8
+ const Separator = React.forwardRef<
9
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11
+ >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
12
+ <SeparatorPrimitive.Root
13
+ ref={ref}
14
+ decorative={decorative}
15
+ orientation={orientation}
16
+ className={cn(
17
+ "shrink-0 bg-border",
18
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ ));
24
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
25
+
26
+ export { Separator };
@@ -0,0 +1,264 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "./utils";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Types
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface SideNavProps extends React.ComponentPropsWithoutRef<"aside"> {
11
+ /** "collapsed" renders icon-only at 60px; "expanded" renders full at 240px (default) */
12
+ width?: "collapsed" | "expanded";
13
+ /** Controlled collapsed state */
14
+ isCollapsed?: boolean;
15
+ /** Called when the sidebar requests a collapse/expand */
16
+ onCollapse?: (collapsed: boolean) => void;
17
+ }
18
+
19
+ export interface SideNavItemProps extends React.ComponentPropsWithoutRef<"a"> {
20
+ /** Icon element — required, rendered at size-4 shrink-0 */
21
+ icon: React.ReactNode;
22
+ /** Visible label — hidden when sidebar is collapsed */
23
+ label: string;
24
+ /** Highlights the item as the current route */
25
+ isActive?: boolean;
26
+ /** Navigable href — renders an <a> when provided */
27
+ href?: string;
28
+ /** Click handler */
29
+ onClick?: React.MouseEventHandler<HTMLElement>;
30
+ /** Optional badge slot rendered after the label */
31
+ badge?: React.ReactNode;
32
+ /** Injected by SideNavSection — consumers should not pass this */
33
+ _collapsed?: boolean;
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Context
38
+ // ---------------------------------------------------------------------------
39
+
40
+ interface SideNavContextValue {
41
+ isCollapsed: boolean;
42
+ }
43
+
44
+ const SideNavContext = React.createContext<SideNavContextValue>({
45
+ isCollapsed: false,
46
+ });
47
+
48
+ function useSideNavContext() {
49
+ return React.useContext(SideNavContext);
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // SideNav
54
+ // ---------------------------------------------------------------------------
55
+
56
+ const SideNav = React.forwardRef<HTMLElement, SideNavProps>(
57
+ (
58
+ {
59
+ className,
60
+ width = "expanded",
61
+ isCollapsed: isCollapsedProp,
62
+ onCollapse,
63
+ children,
64
+ ...props
65
+ },
66
+ ref
67
+ ) => {
68
+ const collapsed =
69
+ isCollapsedProp !== undefined ? isCollapsedProp : width === "collapsed";
70
+
71
+ return (
72
+ <SideNavContext.Provider value={{ isCollapsed: collapsed }}>
73
+ <aside
74
+ ref={ref}
75
+ data-slot="side-nav"
76
+ data-collapsed={collapsed}
77
+ className={cn(
78
+ "flex flex-col bg-sidebar border-r border-sidebar-border",
79
+ "transition-all duration-pg-normal ease-pg-standard",
80
+ collapsed ? "w-[60px]" : "w-[240px]",
81
+ className
82
+ )}
83
+ {...props}
84
+ >
85
+ {children}
86
+ </aside>
87
+ </SideNavContext.Provider>
88
+ );
89
+ }
90
+ );
91
+ SideNav.displayName = "SideNav";
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // SideNavHeader
95
+ // ---------------------------------------------------------------------------
96
+
97
+ const SideNavHeader = React.forwardRef<
98
+ HTMLDivElement,
99
+ React.ComponentPropsWithoutRef<"div">
100
+ >(({ className, ...props }, ref) => (
101
+ <div
102
+ ref={ref}
103
+ data-slot="side-nav-header"
104
+ className={cn(
105
+ "px-4 py-4 border-b border-sidebar-border shrink-0",
106
+ className
107
+ )}
108
+ {...props}
109
+ />
110
+ ));
111
+ SideNavHeader.displayName = "SideNavHeader";
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // SideNavSection
115
+ // ---------------------------------------------------------------------------
116
+
117
+ interface SideNavSectionProps extends React.ComponentPropsWithoutRef<"div"> {
118
+ /** Optional section label — hidden when sidebar is collapsed */
119
+ label?: string;
120
+ }
121
+
122
+ const SideNavSection = React.forwardRef<HTMLDivElement, SideNavSectionProps>(
123
+ ({ className, label, children, ...props }, ref) => {
124
+ const { isCollapsed } = useSideNavContext();
125
+
126
+ return (
127
+ <div
128
+ ref={ref}
129
+ data-slot="side-nav-section"
130
+ className={cn("px-3 py-2", className)}
131
+ {...props}
132
+ >
133
+ {label && !isCollapsed && (
134
+ <p className="mb-1 px-3 text-xs font-medium text-muted-foreground uppercase tracking-wider select-none">
135
+ {label}
136
+ </p>
137
+ )}
138
+ {children}
139
+ </div>
140
+ );
141
+ }
142
+ );
143
+ SideNavSection.displayName = "SideNavSection";
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // SideNavItem
147
+ // ---------------------------------------------------------------------------
148
+
149
+ const SideNavItem = React.forwardRef<HTMLAnchorElement, SideNavItemProps>(
150
+ (
151
+ {
152
+ className,
153
+ icon,
154
+ label,
155
+ isActive = false,
156
+ href,
157
+ onClick,
158
+ badge,
159
+ _collapsed,
160
+ children,
161
+ ...props
162
+ },
163
+ ref
164
+ ) => {
165
+ const { isCollapsed } = useSideNavContext();
166
+ const collapsed = _collapsed !== undefined ? _collapsed : isCollapsed;
167
+
168
+ const baseClassName = cn(
169
+ "flex items-center gap-3 px-3 py-2 rounded-lg text-sm cursor-pointer",
170
+ "transition-colors duration-pg-fast ease-pg-standard",
171
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35",
172
+ "disabled:cursor-not-allowed disabled:opacity-50",
173
+ isActive
174
+ ? "bg-primary/10 text-primary font-medium"
175
+ : "text-sidebar-foreground hover:bg-muted/50",
176
+ collapsed && "justify-center px-0",
177
+ className
178
+ );
179
+
180
+ const content = (
181
+ <>
182
+ <span className="size-4 shrink-0 flex items-center justify-center">
183
+ {icon}
184
+ </span>
185
+ {!collapsed && (
186
+ <>
187
+ <span className="flex-1 truncate">{label}</span>
188
+ {badge && <span className="shrink-0">{badge}</span>}
189
+ </>
190
+ )}
191
+ </>
192
+ );
193
+
194
+ if (href) {
195
+ return (
196
+ <a
197
+ ref={ref}
198
+ href={href}
199
+ title={collapsed ? label : undefined}
200
+ aria-current={isActive ? "page" : undefined}
201
+ className={baseClassName}
202
+ onClick={onClick as React.MouseEventHandler<HTMLAnchorElement>}
203
+ {...props}
204
+ >
205
+ {content}
206
+ </a>
207
+ );
208
+ }
209
+
210
+ return (
211
+ <a
212
+ ref={ref}
213
+ role="button"
214
+ tabIndex={0}
215
+ title={collapsed ? label : undefined}
216
+ aria-current={isActive ? "page" : undefined}
217
+ className={baseClassName}
218
+ onClick={onClick as React.MouseEventHandler<HTMLAnchorElement>}
219
+ onKeyDown={(e) => {
220
+ if (e.key === "Enter" || e.key === " ") {
221
+ e.preventDefault();
222
+ onClick?.(e as unknown as React.MouseEvent<HTMLElement>);
223
+ }
224
+ }}
225
+ {...props}
226
+ >
227
+ {content}
228
+ </a>
229
+ );
230
+ }
231
+ );
232
+ SideNavItem.displayName = "SideNavItem";
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // SideNavFooter
236
+ // ---------------------------------------------------------------------------
237
+
238
+ const SideNavFooter = React.forwardRef<
239
+ HTMLDivElement,
240
+ React.ComponentPropsWithoutRef<"div">
241
+ >(({ className, ...props }, ref) => (
242
+ <div
243
+ ref={ref}
244
+ data-slot="side-nav-footer"
245
+ className={cn(
246
+ "mt-auto border-t border-sidebar-border px-3 py-3 shrink-0",
247
+ className
248
+ )}
249
+ {...props}
250
+ />
251
+ ));
252
+ SideNavFooter.displayName = "SideNavFooter";
253
+
254
+ // ---------------------------------------------------------------------------
255
+ // Exports
256
+ // ---------------------------------------------------------------------------
257
+
258
+ export {
259
+ SideNav,
260
+ SideNavHeader,
261
+ SideNavSection,
262
+ SideNavItem,
263
+ SideNavFooter,
264
+ };
@@ -0,0 +1,74 @@
1
+ import { cn } from "./utils";
2
+
3
+ interface ShimmerProps {
4
+ className?: string;
5
+ rounded?: "sm" | "md" | "lg" | "full";
6
+ }
7
+
8
+ export function Shimmer({ className, rounded = "md" }: ShimmerProps) {
9
+ const r = { sm: "rounded", md: "rounded-lg", lg: "rounded-xl", full: "rounded-full" }[rounded];
10
+ return <div className={cn("shimmer", r, className)} />;
11
+ }
12
+
13
+ export function StatCardSkeleton() {
14
+ return (
15
+ <div className="bg-card text-card-foreground rounded-xl p-5 flex flex-col gap-3 border border-border shadow-sm">
16
+ <div className="flex items-center justify-between">
17
+ <Shimmer className="h-3 w-28" />
18
+ <Shimmer className="h-10 w-10" rounded="full" />
19
+ </div>
20
+ <Shimmer className="h-9 w-44 mt-1" />
21
+ <Shimmer className="h-3 w-32" />
22
+ </div>
23
+ );
24
+ }
25
+
26
+ export function TableRowSkeleton({
27
+ cols = 6,
28
+ comfortable = false,
29
+ density: densityProp,
30
+ snug = false,
31
+ }: {
32
+ cols?: number;
33
+ /** @deprecated prefer `density` */
34
+ comfortable?: boolean;
35
+ density?: "default" | "comfortable" | "compact";
36
+ snug?: boolean;
37
+ }) {
38
+ const density =
39
+ densityProp ??
40
+ (comfortable ? "comfortable" : "default");
41
+ const cellPad =
42
+ density === "comfortable"
43
+ ? "px-5 py-4"
44
+ : density === "compact"
45
+ ? snug
46
+ ? "pl-1.5 pr-2.5 py-2.5"
47
+ : "px-3 py-2.5"
48
+ : "px-4 py-3.5";
49
+ return (
50
+ <tr
51
+ className={cn(
52
+ "border-b border-border/60",
53
+ density === "comfortable" && "min-h-[56px]",
54
+ density === "compact" && "min-h-[44px]"
55
+ )}
56
+ >
57
+ {Array.from({ length: cols }).map((_, i) => (
58
+ <td key={i} className={cellPad}>
59
+ <Shimmer className={cn("h-3.5", i === 0 ? "w-20" : i === cols - 1 ? "w-14" : "w-28")} />
60
+ </td>
61
+ ))}
62
+ </tr>
63
+ );
64
+ }
65
+
66
+ export function ChartSkeleton({ height = "h-48" }: { height?: string }) {
67
+ return (
68
+ <div className={cn("flex items-end gap-2 px-2", height)}>
69
+ {[55, 72, 40, 88, 65, 82, 48, 95, 60, 70].map((h, i) => (
70
+ <div key={i} className="flex-1 shimmer rounded-t-md" style={{ height: `${h}%` }} />
71
+ ))}
72
+ </div>
73
+ );
74
+ }
package/src/slider.tsx ADDED
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as SliderPrimitive from "@radix-ui/react-slider";
5
+ import { cn } from "./utils";
6
+
7
+ export interface SliderProps extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {}
8
+
9
+ const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, SliderProps>(
10
+ ({ className, ...props }, ref) => (
11
+ <SliderPrimitive.Root
12
+ ref={ref}
13
+ className={cn("relative flex w-full touch-none select-none items-center", className)}
14
+ {...props}
15
+ >
16
+ <SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-muted">
17
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
18
+ </SliderPrimitive.Track>
19
+ <SliderPrimitive.Thumb className="block h-[18px] w-[18px] rounded-full border-2 border-primary bg-card shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50" />
20
+ </SliderPrimitive.Root>
21
+ )
22
+ );
23
+ Slider.displayName = SliderPrimitive.Root.displayName;
24
+
25
+ export { Slider };
package/src/sonner.tsx ADDED
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { useTheme } from "next-themes";
4
+ import { Toaster as Sonner, type ToasterProps } from "sonner";
5
+ import { useEffect, useState } from "react";
6
+
7
+ /** Drop-in toast host; pair with `toast` from `sonner`. Resolves theme via next-themes when mounted. */
8
+ export function Toaster({ theme, ...props }: ToasterProps) {
9
+ const { resolvedTheme } = useTheme();
10
+ const [mounted, setMounted] = useState(false);
11
+ useEffect(() => setMounted(true), []);
12
+
13
+ const resolved =
14
+ theme ?? (mounted && resolvedTheme === "dark" ? "dark" : "light");
15
+
16
+ return (
17
+ <Sonner
18
+ theme={resolved}
19
+ position="bottom-right"
20
+ toastOptions={{
21
+ classNames: {
22
+ toast:
23
+ "bg-[var(--popover)] text-[var(--popover-foreground)] border-[var(--border)] shadow-lg rounded-[10px] text-[13px]",
24
+ description: "text-[var(--muted-foreground)]",
25
+ },
26
+ }}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ export { toast } from "sonner";
@@ -0,0 +1,54 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Loader2 } from "lucide-react";
5
+
6
+ import { cn } from "./utils";
7
+
8
+ const sizeMap = {
9
+ xs: 12,
10
+ sm: 16,
11
+ md: 20,
12
+ lg: 28,
13
+ xl: 36,
14
+ } as const;
15
+
16
+ const colorMap = {
17
+ primary: "text-primary",
18
+ muted: "text-muted-foreground",
19
+ white: "text-white",
20
+ inherit: "",
21
+ } as const;
22
+
23
+ export interface SpinnerProps extends React.HTMLAttributes<HTMLSpanElement> {
24
+ size?: keyof typeof sizeMap;
25
+ color?: keyof typeof colorMap;
26
+ }
27
+
28
+ const Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(
29
+ ({ className, size = "md", color = "primary", ...props }, ref) => {
30
+ const px = sizeMap[size];
31
+
32
+ return (
33
+ <span
34
+ ref={ref}
35
+ data-slot="spinner"
36
+ role="status"
37
+ aria-label="Loading"
38
+ className={cn("inline-flex items-center justify-center", colorMap[color], className)}
39
+ {...props}
40
+ >
41
+ <Loader2
42
+ width={px}
43
+ height={px}
44
+ className="animate-spin"
45
+ aria-hidden="true"
46
+ />
47
+ <span className="sr-only">Loading...</span>
48
+ </span>
49
+ );
50
+ }
51
+ );
52
+ Spinner.displayName = "Spinner";
53
+
54
+ export { Spinner };