@olwiba/ui 0.1.13 → 0.1.15
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.d.ts +239 -5
- package/dist/index.js +786 -119
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/app/AuthSection.tsx +155 -54
- package/src/app/BillingPanel.tsx +149 -0
- package/src/app/OnboardingWizard.tsx +115 -0
- package/src/app/SettingsSection.tsx +29 -0
- package/src/app/TeamMembersPanel.tsx +174 -0
- package/src/components/CommandMenu.tsx +100 -0
- package/src/components/DataTable.tsx +202 -0
- package/src/components/FileUpload.tsx +195 -0
- package/src/components/Notify.tsx +94 -0
- package/src/index.ts +34 -0
package/dist/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { cn } from './chunk-AMWLSHD6.js';
|
|
2
2
|
export { MdxContent, cn } from './chunk-AMWLSHD6.js';
|
|
3
|
-
import * as
|
|
3
|
+
import * as React30 from 'react';
|
|
4
4
|
import { useRef, useEffect, useState } from 'react';
|
|
5
|
-
import { Button as Button$1, Card as Card$1, Input as Input$1, Textarea as Textarea$1, Checkbox as Checkbox$1, Switch as Switch$1, useIsMobile, UIVariantProvider, Badge as Badge$1, cn as cn$1, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Avatar, AvatarImage, AvatarFallback, useUIVariant, Separator, Accordion, AccordionItem, AccordionTrigger, AccordionContent, Label, Sheet, SheetTrigger, SheetContent, StatusIndicator, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandSeparator, CommandGroup, CommandItem, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, ContextMenu as ContextMenu$1, ContextMenuTrigger, ContextMenuContent, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, Spinner, Popover, PopoverTrigger, PopoverContent, SidebarProvider, SidebarInset, CardHeader, CardTitle, CardDescription, CardContent, ContextMenuSeparator, ContextMenuLabel, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, ContextMenuItem, ContextMenuShortcut, Sidebar, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarFooter, SidebarTrigger, useSidebar,
|
|
6
|
-
export { CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@olwiba/cn';
|
|
5
|
+
import { Button as Button$1, Card as Card$1, Input as Input$1, Textarea as Textarea$1, Checkbox as Checkbox$1, Switch as Switch$1, useIsMobile, UIVariantProvider, Badge as Badge$1, cn as cn$1, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Avatar, AvatarImage, AvatarFallback, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Dialog, DialogTrigger, Progress, useUIVariant, Separator, Accordion, AccordionItem, AccordionTrigger, AccordionContent, Label, Sheet, SheetTrigger, SheetContent, StatusIndicator, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandSeparator, CommandGroup, CommandItem, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, ContextMenu as ContextMenu$1, ContextMenuTrigger, ContextMenuContent, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, CommandShortcut, Spinner, Popover, PopoverTrigger, PopoverContent, SidebarProvider, SidebarInset, CardHeader, CardTitle, CardDescription, CardContent, InputOTP, InputOTPGroup, InputOTPSlot, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, ContextMenuSeparator, ContextMenuLabel, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, ContextMenuItem, ContextMenuShortcut, Sidebar, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarFooter, SidebarTrigger, useSidebar, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup } from '@olwiba/cn';
|
|
6
|
+
export { CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Enchanted } from '@olwiba/cn';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
-
import {
|
|
8
|
+
import { Search, ArrowUp, ArrowDown, ArrowUpDown, ChevronLeft, ChevronRight, MoreHorizontal, UserPlus, Download, CreditCard, Check, ArrowLeft, ArrowUpRight, X, ArrowRight, Sparkles, Rocket, Minus, Quote, Star, Twitter, Github, Linkedin, Mail, MessageSquare, ChevronDown, Send, Menu, UploadCloud, File, Loader2, CheckCircle2, AlertCircle, TrendingUp, TrendingDown, Sun, Moon, Palette, AlertTriangle, Inbox, Info, Layers, LayoutGrid, ShieldCheck, Building2, MoreVerticalIcon, CreditCardIcon, BellIcon, LogOutIcon } from 'lucide-react';
|
|
9
|
+
import { useReactTable, getPaginationRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
|
|
10
|
+
import { toast } from 'sonner';
|
|
9
11
|
|
|
10
|
-
var OlwibaUIContext =
|
|
12
|
+
var OlwibaUIContext = React30.createContext({
|
|
11
13
|
isMobile: false,
|
|
12
14
|
mode: "default",
|
|
13
15
|
setMode: () => {
|
|
@@ -16,17 +18,17 @@ var OlwibaUIContext = React29.createContext({
|
|
|
16
18
|
function OlwibaUIProvider({ children, isMobile: isMobileProp, mode: initialMode = "default" }) {
|
|
17
19
|
const detectedMobile = useIsMobile();
|
|
18
20
|
const isMobile = isMobileProp ?? detectedMobile;
|
|
19
|
-
const [mode, setMode] =
|
|
21
|
+
const [mode, setMode] = React30.useState(initialMode);
|
|
20
22
|
const variant = mode !== "default" ? mode : void 0;
|
|
21
23
|
return /* @__PURE__ */ jsx(OlwibaUIContext.Provider, { value: { isMobile, mode, setMode }, children: /* @__PURE__ */ jsx(UIVariantProvider, { mode: variant, children }) });
|
|
22
24
|
}
|
|
23
25
|
function useOlwibaUI() {
|
|
24
|
-
return
|
|
26
|
+
return React30.useContext(OlwibaUIContext);
|
|
25
27
|
}
|
|
26
28
|
function useUIMode() {
|
|
27
|
-
return
|
|
29
|
+
return React30.useContext(OlwibaUIContext).mode;
|
|
28
30
|
}
|
|
29
|
-
var Button =
|
|
31
|
+
var Button = React30.forwardRef(
|
|
30
32
|
({ mode, ...props }, ref) => {
|
|
31
33
|
const ctxMode = useUIMode();
|
|
32
34
|
const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
|
|
@@ -34,7 +36,7 @@ var Button = React29.forwardRef(
|
|
|
34
36
|
}
|
|
35
37
|
);
|
|
36
38
|
Button.displayName = "Button";
|
|
37
|
-
var Card =
|
|
39
|
+
var Card = React30.forwardRef(
|
|
38
40
|
({ mode, ...props }, ref) => {
|
|
39
41
|
const ctxMode = useUIMode();
|
|
40
42
|
const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
|
|
@@ -45,7 +47,7 @@ Card.displayName = "Card";
|
|
|
45
47
|
function Badge(props) {
|
|
46
48
|
return /* @__PURE__ */ jsx(Badge$1, { ...props });
|
|
47
49
|
}
|
|
48
|
-
var Input =
|
|
50
|
+
var Input = React30.forwardRef(
|
|
49
51
|
({ mode, ...props }, ref) => {
|
|
50
52
|
const ctxMode = useUIMode();
|
|
51
53
|
const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
|
|
@@ -53,7 +55,7 @@ var Input = React29.forwardRef(
|
|
|
53
55
|
}
|
|
54
56
|
);
|
|
55
57
|
Input.displayName = "Input";
|
|
56
|
-
var Textarea =
|
|
58
|
+
var Textarea = React30.forwardRef(
|
|
57
59
|
({ mode, ...props }, ref) => {
|
|
58
60
|
const ctxMode = useUIMode();
|
|
59
61
|
const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
|
|
@@ -61,13 +63,13 @@ var Textarea = React29.forwardRef(
|
|
|
61
63
|
}
|
|
62
64
|
);
|
|
63
65
|
Textarea.displayName = "Textarea";
|
|
64
|
-
var Checkbox =
|
|
66
|
+
var Checkbox = React30.forwardRef(({ mode, ...props }, ref) => {
|
|
65
67
|
const ctxMode = useUIMode();
|
|
66
68
|
const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
|
|
67
69
|
return /* @__PURE__ */ jsx(Checkbox$1, { mode: resolvedMode, ...props, ref });
|
|
68
70
|
});
|
|
69
71
|
Checkbox.displayName = "Checkbox";
|
|
70
|
-
var Switch =
|
|
72
|
+
var Switch = React30.forwardRef(({ mode: _mode, ...props }, ref) => {
|
|
71
73
|
return /* @__PURE__ */ jsx(Switch$1, { ...props, ref });
|
|
72
74
|
});
|
|
73
75
|
Switch.displayName = "Switch";
|
|
@@ -481,6 +483,13 @@ function SplitAuth({ children, panel }) {
|
|
|
481
483
|
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-6 lg:p-10", children })
|
|
482
484
|
] });
|
|
483
485
|
}
|
|
486
|
+
var authCopy = {
|
|
487
|
+
signin: { title: "Sign in", description: "Enter your email and password to continue.", submit: "Sign in" },
|
|
488
|
+
signup: { title: "Create an account", description: "Enter your details to create your account.", submit: "Create account" },
|
|
489
|
+
"forgot-password": { title: "Reset your password", description: "Enter the email on your account and we\u2019ll send you a link to reset your password.", submit: "Send reset link" },
|
|
490
|
+
"reset-password": { title: "Choose a new password", description: "Your new password must be different from previous passwords.", submit: "Reset password" },
|
|
491
|
+
verify: { title: "Enter your code", description: "We sent a verification code to your email.", submit: "Verify" }
|
|
492
|
+
};
|
|
484
493
|
function DefaultForm({
|
|
485
494
|
mode = "signin",
|
|
486
495
|
onSubmit,
|
|
@@ -488,8 +497,12 @@ function DefaultForm({
|
|
|
488
497
|
signUpHref = "#",
|
|
489
498
|
signInHref = "#",
|
|
490
499
|
forgotPasswordHref = "#",
|
|
500
|
+
onResend,
|
|
501
|
+
destination,
|
|
502
|
+
codeLength = 6,
|
|
491
503
|
brand,
|
|
492
504
|
error,
|
|
505
|
+
success,
|
|
493
506
|
loading,
|
|
494
507
|
renderLink = defaultRenderLink2,
|
|
495
508
|
footer,
|
|
@@ -497,15 +510,24 @@ function DefaultForm({
|
|
|
497
510
|
defaultPassword
|
|
498
511
|
}) {
|
|
499
512
|
const isSignUp = mode === "signup";
|
|
513
|
+
const isForgotPassword = mode === "forgot-password";
|
|
514
|
+
const isResetPassword = mode === "reset-password";
|
|
515
|
+
const isVerify = mode === "verify";
|
|
516
|
+
const [code, setCode] = React30.useState("");
|
|
500
517
|
const hasPrefill = !!(defaultEmail || defaultPassword);
|
|
501
518
|
const prefillStyle = (active) => active ? { animation: "auth-prefill 1.6s ease-out 0.35s 1 both" } : void 0;
|
|
519
|
+
const copy = authCopy[mode];
|
|
502
520
|
return /* @__PURE__ */ jsxs(Card, { className: "w-full", children: [
|
|
503
521
|
hasPrefill && // Theme tokens hold full color values (oklch), so opacity must come from color-mix, not hsl(var()/a)
|
|
504
522
|
/* @__PURE__ */ jsx("style", { children: `@keyframes auth-prefill{0%{box-shadow:0 0 0 0 transparent}40%{box-shadow:0 0 0 3px color-mix(in oklab,var(--primary) 45%,transparent),0 0 16px 2px color-mix(in oklab,var(--primary) 35%,transparent)}100%{box-shadow:0 0 0 0 transparent}}` }),
|
|
505
523
|
/* @__PURE__ */ jsxs(CardHeader, { children: [
|
|
506
524
|
brand && /* @__PURE__ */ jsx("div", { className: "mb-2", children: brand }),
|
|
507
|
-
/* @__PURE__ */ jsx(CardTitle, { children:
|
|
508
|
-
/* @__PURE__ */ jsx(CardDescription, { children:
|
|
525
|
+
/* @__PURE__ */ jsx(CardTitle, { children: copy.title }),
|
|
526
|
+
/* @__PURE__ */ jsx(CardDescription, { children: isVerify && destination ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
527
|
+
"We sent a verification code to ",
|
|
528
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: destination }),
|
|
529
|
+
"."
|
|
530
|
+
] }) : copy.description })
|
|
509
531
|
] }),
|
|
510
532
|
/* @__PURE__ */ jsxs(CardContent, { className: "space-y-4", children: [
|
|
511
533
|
/* @__PURE__ */ jsxs("form", { onSubmit, className: "space-y-4", children: [
|
|
@@ -513,7 +535,7 @@ function DefaultForm({
|
|
|
513
535
|
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-name", children: "Name" }),
|
|
514
536
|
/* @__PURE__ */ jsx(Input, { id: "auth-name", name: "name", type: "text", placeholder: "Your name", autoComplete: "name" })
|
|
515
537
|
] }),
|
|
516
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
538
|
+
(mode === "signin" || mode === "signup" || isForgotPassword) && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
517
539
|
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-email", children: "Email address" }),
|
|
518
540
|
/* @__PURE__ */ jsx(
|
|
519
541
|
Input,
|
|
@@ -528,7 +550,7 @@ function DefaultForm({
|
|
|
528
550
|
}
|
|
529
551
|
)
|
|
530
552
|
] }),
|
|
531
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
553
|
+
(mode === "signin" || mode === "signup") && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
532
554
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
533
555
|
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-password", children: "Password" }),
|
|
534
556
|
!isSignUp && forgotPasswordHref && renderLink({
|
|
@@ -550,13 +572,33 @@ function DefaultForm({
|
|
|
550
572
|
}
|
|
551
573
|
)
|
|
552
574
|
] }),
|
|
575
|
+
isResetPassword && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
576
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
577
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-new-password", children: "New password" }),
|
|
578
|
+
/* @__PURE__ */ jsx(Input, { id: "auth-new-password", name: "password", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: "new-password" })
|
|
579
|
+
] }),
|
|
580
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
581
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-confirm-password", children: "Confirm password" }),
|
|
582
|
+
/* @__PURE__ */ jsx(Input, { id: "auth-confirm-password", name: "confirmPassword", type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: "new-password" })
|
|
583
|
+
] })
|
|
584
|
+
] }),
|
|
585
|
+
isVerify && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
586
|
+
/* @__PURE__ */ jsx("input", { type: "hidden", name: "code", value: code }),
|
|
587
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx(InputOTP, { maxLength: codeLength, value: code, onChange: setCode, containerClassName: "justify-center", children: /* @__PURE__ */ jsx(InputOTPGroup, { children: Array.from({ length: codeLength }).map((_, i) => /* @__PURE__ */ jsx(InputOTPSlot, { index: i }, i)) }) }) })
|
|
588
|
+
] }),
|
|
553
589
|
error && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: error }),
|
|
590
|
+
success && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-primary", children: success }),
|
|
554
591
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
555
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", disabled: loading, children: loading ? "Please wait\u2026" :
|
|
556
|
-
onSso && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", className: "w-full", onClick: onSso, disabled: loading, children: "Use SSO" })
|
|
592
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", disabled: loading || isVerify && code.length < codeLength, children: loading ? "Please wait\u2026" : copy.submit }),
|
|
593
|
+
onSso && mode === "signin" && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", className: "w-full", onClick: onSso, disabled: loading, children: "Use SSO" })
|
|
557
594
|
] })
|
|
558
595
|
] }),
|
|
559
|
-
/* @__PURE__ */
|
|
596
|
+
isVerify && onResend && /* @__PURE__ */ jsxs("p", { className: "text-center text-xs text-muted-foreground", children: [
|
|
597
|
+
"Didn\u2019t get a code?",
|
|
598
|
+
" ",
|
|
599
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onResend, className: "text-foreground underline underline-offset-4", children: "Resend" })
|
|
600
|
+
] }),
|
|
601
|
+
(mode === "signin" || mode === "signup") && /* @__PURE__ */ jsx("p", { className: "text-center text-xs text-muted-foreground", children: isSignUp ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
560
602
|
"Already have an account?",
|
|
561
603
|
" ",
|
|
562
604
|
renderLink({ href: signInHref, className: "text-foreground underline underline-offset-4", children: "Sign in" })
|
|
@@ -565,6 +607,7 @@ function DefaultForm({
|
|
|
565
607
|
" ",
|
|
566
608
|
renderLink({ href: signUpHref, className: "text-foreground underline underline-offset-4", children: "Create an account" })
|
|
567
609
|
] }) }),
|
|
610
|
+
(isForgotPassword || isResetPassword || isVerify) && /* @__PURE__ */ jsx("p", { className: "text-center text-xs text-muted-foreground", children: renderLink({ href: signInHref, className: "text-foreground underline underline-offset-4", children: "Back to sign in" }) }),
|
|
568
611
|
footer && /* @__PURE__ */ jsx("div", { children: footer })
|
|
569
612
|
] })
|
|
570
613
|
] });
|
|
@@ -584,6 +627,386 @@ function AuthSection({
|
|
|
584
627
|
const form = children ?? /* @__PURE__ */ jsx(DefaultForm, { ...restFormProps });
|
|
585
628
|
return /* @__PURE__ */ jsx(CenteredAuth, { brand, className, children: form });
|
|
586
629
|
}
|
|
630
|
+
function SettingsSection({ title, description, children, danger = false, className }) {
|
|
631
|
+
return /* @__PURE__ */ jsxs("div", { className: cn$1("grid grid-cols-1 gap-x-8 gap-y-6 py-10 sm:grid-cols-3", className), children: [
|
|
632
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
633
|
+
/* @__PURE__ */ jsx("h2", { className: cn$1("text-base font-semibold", danger && "text-destructive"), children: title }),
|
|
634
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })
|
|
635
|
+
] }),
|
|
636
|
+
/* @__PURE__ */ jsx("div", { className: "sm:col-span-2", children })
|
|
637
|
+
] });
|
|
638
|
+
}
|
|
639
|
+
var selectColumn = {
|
|
640
|
+
id: "__select",
|
|
641
|
+
header: ({ table }) => /* @__PURE__ */ jsx(
|
|
642
|
+
Checkbox$1,
|
|
643
|
+
{
|
|
644
|
+
checked: table.getIsAllPageRowsSelected() || table.getIsSomePageRowsSelected() && "indeterminate",
|
|
645
|
+
onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value),
|
|
646
|
+
"aria-label": "Select all"
|
|
647
|
+
}
|
|
648
|
+
),
|
|
649
|
+
cell: ({ row }) => /* @__PURE__ */ jsx(
|
|
650
|
+
Checkbox$1,
|
|
651
|
+
{
|
|
652
|
+
checked: row.getIsSelected(),
|
|
653
|
+
onCheckedChange: (value) => row.toggleSelected(!!value),
|
|
654
|
+
onClick: (e) => e.stopPropagation(),
|
|
655
|
+
"aria-label": "Select row"
|
|
656
|
+
}
|
|
657
|
+
),
|
|
658
|
+
enableSorting: false
|
|
659
|
+
};
|
|
660
|
+
function DataTable({
|
|
661
|
+
columns,
|
|
662
|
+
data,
|
|
663
|
+
searchKey,
|
|
664
|
+
searchPlaceholder = "Search\u2026",
|
|
665
|
+
selectable = false,
|
|
666
|
+
onSelectionChange,
|
|
667
|
+
pageSize = 10,
|
|
668
|
+
toolbar,
|
|
669
|
+
onRowClick,
|
|
670
|
+
emptyMessage = "No results.",
|
|
671
|
+
className
|
|
672
|
+
}) {
|
|
673
|
+
const [sorting, setSorting] = React30.useState([]);
|
|
674
|
+
const [rowSelection, setRowSelection] = React30.useState({});
|
|
675
|
+
const [globalFilter, setGlobalFilter] = React30.useState("");
|
|
676
|
+
const allColumns = React30.useMemo(
|
|
677
|
+
() => selectable ? [selectColumn, ...columns] : columns,
|
|
678
|
+
[selectable, columns]
|
|
679
|
+
);
|
|
680
|
+
const table = useReactTable({
|
|
681
|
+
data,
|
|
682
|
+
columns: allColumns,
|
|
683
|
+
state: { sorting, rowSelection, globalFilter },
|
|
684
|
+
onSortingChange: setSorting,
|
|
685
|
+
onRowSelectionChange: setRowSelection,
|
|
686
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
687
|
+
globalFilterFn: (row, _columnId, filterValue) => {
|
|
688
|
+
if (!searchKey) return true;
|
|
689
|
+
const value = row.getValue(searchKey);
|
|
690
|
+
return String(value ?? "").toLowerCase().includes(String(filterValue).toLowerCase());
|
|
691
|
+
},
|
|
692
|
+
getCoreRowModel: getCoreRowModel(),
|
|
693
|
+
getSortedRowModel: getSortedRowModel(),
|
|
694
|
+
...pageSize > 0 ? { getPaginationRowModel: getPaginationRowModel() } : {},
|
|
695
|
+
initialState: pageSize > 0 ? { pagination: { pageSize } } : void 0,
|
|
696
|
+
enableRowSelection: selectable
|
|
697
|
+
});
|
|
698
|
+
React30.useEffect(() => {
|
|
699
|
+
if (!onSelectionChange) return;
|
|
700
|
+
onSelectionChange(table.getSelectedRowModel().rows.map((r) => r.original));
|
|
701
|
+
}, [rowSelection]);
|
|
702
|
+
const rows = table.getRowModel().rows;
|
|
703
|
+
return /* @__PURE__ */ jsxs("div", { className: cn$1("space-y-4", className), children: [
|
|
704
|
+
(searchKey || toolbar) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
|
|
705
|
+
searchKey ? /* @__PURE__ */ jsxs("div", { className: "relative max-w-sm flex-1", children: [
|
|
706
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
|
|
707
|
+
/* @__PURE__ */ jsx(
|
|
708
|
+
Input,
|
|
709
|
+
{
|
|
710
|
+
value: globalFilter,
|
|
711
|
+
onChange: (e) => setGlobalFilter(e.target.value),
|
|
712
|
+
placeholder: searchPlaceholder,
|
|
713
|
+
className: "pl-8"
|
|
714
|
+
}
|
|
715
|
+
)
|
|
716
|
+
] }) : /* @__PURE__ */ jsx("div", {}),
|
|
717
|
+
toolbar
|
|
718
|
+
] }),
|
|
719
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border", children: /* @__PURE__ */ jsxs(Table, { children: [
|
|
720
|
+
/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => {
|
|
721
|
+
const canSort = header.column.getCanSort();
|
|
722
|
+
const sortDir = header.column.getIsSorted();
|
|
723
|
+
return /* @__PURE__ */ jsx(TableHead, { children: header.isPlaceholder ? null : canSort ? /* @__PURE__ */ jsxs(
|
|
724
|
+
"button",
|
|
725
|
+
{
|
|
726
|
+
type: "button",
|
|
727
|
+
className: "flex items-center gap-1.5 font-medium hover:text-foreground",
|
|
728
|
+
onClick: header.column.getToggleSortingHandler(),
|
|
729
|
+
children: [
|
|
730
|
+
flexRender(header.column.columnDef.header, header.getContext()),
|
|
731
|
+
sortDir === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "size-3.5" }) : sortDir === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "size-3.5 text-muted-foreground/50" })
|
|
732
|
+
]
|
|
733
|
+
}
|
|
734
|
+
) : flexRender(header.column.columnDef.header, header.getContext()) }, header.id);
|
|
735
|
+
}) }, headerGroup.id)) }),
|
|
736
|
+
/* @__PURE__ */ jsx(TableBody, { children: rows.length ? rows.map((row) => /* @__PURE__ */ jsx(
|
|
737
|
+
TableRow,
|
|
738
|
+
{
|
|
739
|
+
"data-state": row.getIsSelected() ? "selected" : void 0,
|
|
740
|
+
onClick: () => onRowClick?.(row.original),
|
|
741
|
+
className: cn$1(onRowClick && "cursor-pointer"),
|
|
742
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
|
|
743
|
+
},
|
|
744
|
+
row.id
|
|
745
|
+
)) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: allColumns.length, className: "h-24 text-center text-muted-foreground", children: emptyMessage }) }) })
|
|
746
|
+
] }) }),
|
|
747
|
+
pageSize > 0 && table.getPageCount() > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
748
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
749
|
+
selectable && `${table.getFilteredSelectedRowModel().rows.length} of ${table.getFilteredRowModel().rows.length} selected \xB7 `,
|
|
750
|
+
"Page ",
|
|
751
|
+
table.getState().pagination.pageIndex + 1,
|
|
752
|
+
" of ",
|
|
753
|
+
table.getPageCount()
|
|
754
|
+
] }),
|
|
755
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
756
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => table.previousPage(), disabled: !table.getCanPreviousPage(), children: [
|
|
757
|
+
/* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" }),
|
|
758
|
+
" Previous"
|
|
759
|
+
] }),
|
|
760
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => table.nextPage(), disabled: !table.getCanNextPage(), children: [
|
|
761
|
+
"Next ",
|
|
762
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
|
|
763
|
+
] })
|
|
764
|
+
] })
|
|
765
|
+
] })
|
|
766
|
+
] });
|
|
767
|
+
}
|
|
768
|
+
function InviteDialog({ roles, onInvite }) {
|
|
769
|
+
const [email, setEmail] = React30.useState("");
|
|
770
|
+
const [role, setRole] = React30.useState(roles[roles.length - 1] ?? roles[0]);
|
|
771
|
+
return /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
772
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
773
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Invite a team member" }),
|
|
774
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: "They\u2019ll get an email invite to join this workspace." })
|
|
775
|
+
] }),
|
|
776
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4 py-2", children: [
|
|
777
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
778
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "invite-email", children: "Email address" }),
|
|
779
|
+
/* @__PURE__ */ jsx(Input, { id: "invite-email", type: "email", placeholder: "name@company.com", value: email, onChange: (e) => setEmail(e.target.value) })
|
|
780
|
+
] }),
|
|
781
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
782
|
+
/* @__PURE__ */ jsx(Label, { children: "Role" }),
|
|
783
|
+
/* @__PURE__ */ jsxs(Select, { value: role, onValueChange: setRole, children: [
|
|
784
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
785
|
+
/* @__PURE__ */ jsx(SelectContent, { children: roles.map((r) => /* @__PURE__ */ jsx(SelectItem, { value: r, children: r }, r)) })
|
|
786
|
+
] })
|
|
787
|
+
] })
|
|
788
|
+
] }),
|
|
789
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
790
|
+
/* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: "Cancel" }) }),
|
|
791
|
+
/* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { disabled: !email, onClick: () => onInvite?.(email, role), children: "Send invite" }) })
|
|
792
|
+
] })
|
|
793
|
+
] });
|
|
794
|
+
}
|
|
795
|
+
function TeamMembersPanel({
|
|
796
|
+
members,
|
|
797
|
+
roles = ["Owner", "Admin", "Member"],
|
|
798
|
+
onInvite,
|
|
799
|
+
onRoleChange,
|
|
800
|
+
onRemove,
|
|
801
|
+
title = "Team members",
|
|
802
|
+
description = "Manage who has access to this workspace."
|
|
803
|
+
}) {
|
|
804
|
+
const columns = React30.useMemo(() => [
|
|
805
|
+
{
|
|
806
|
+
accessorKey: "name",
|
|
807
|
+
header: "Name",
|
|
808
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
809
|
+
/* @__PURE__ */ jsxs(Avatar, { className: "size-8", children: [
|
|
810
|
+
/* @__PURE__ */ jsx(AvatarImage, { src: row.original.avatar, alt: "" }),
|
|
811
|
+
/* @__PURE__ */ jsx(AvatarFallback, { children: row.original.name.slice(0, 2).toUpperCase() })
|
|
812
|
+
] }),
|
|
813
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
814
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-none", children: row.original.name }),
|
|
815
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: row.original.email })
|
|
816
|
+
] })
|
|
817
|
+
] })
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
accessorKey: "role",
|
|
821
|
+
header: "Role",
|
|
822
|
+
cell: ({ row }) => onRoleChange ? /* @__PURE__ */ jsxs(Select, { value: row.original.role, onValueChange: (value) => onRoleChange(row.original.id, value), children: [
|
|
823
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-32", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
824
|
+
/* @__PURE__ */ jsx(SelectContent, { children: roles.map((r) => /* @__PURE__ */ jsx(SelectItem, { value: r, children: r }, r)) })
|
|
825
|
+
] }) : /* @__PURE__ */ jsx(Badge$1, { variant: "secondary", children: row.original.role })
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
id: "actions",
|
|
829
|
+
header: "",
|
|
830
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
831
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "icon", className: "size-8", children: [
|
|
832
|
+
/* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" }),
|
|
833
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Row actions" })
|
|
834
|
+
] }) }),
|
|
835
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: /* @__PURE__ */ jsx(DropdownMenuItem, { className: "text-destructive", onClick: () => onRemove?.(row.original.id), children: "Remove member" }) })
|
|
836
|
+
] }),
|
|
837
|
+
enableSorting: false
|
|
838
|
+
}
|
|
839
|
+
], [roles, onRoleChange, onRemove]);
|
|
840
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
841
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
|
|
842
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
843
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title }),
|
|
844
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })
|
|
845
|
+
] }),
|
|
846
|
+
/* @__PURE__ */ jsxs(Dialog, { children: [
|
|
847
|
+
/* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { children: [
|
|
848
|
+
/* @__PURE__ */ jsx(UserPlus, { className: "size-4" }),
|
|
849
|
+
" Invite member"
|
|
850
|
+
] }) }),
|
|
851
|
+
/* @__PURE__ */ jsx(InviteDialog, { roles, onInvite })
|
|
852
|
+
] })
|
|
853
|
+
] }),
|
|
854
|
+
/* @__PURE__ */ jsx(DataTable, { columns, data: members, searchKey: "name", searchPlaceholder: "Search members\u2026", pageSize: 0 })
|
|
855
|
+
] });
|
|
856
|
+
}
|
|
857
|
+
var invoiceStatusVariant = {
|
|
858
|
+
paid: "secondary",
|
|
859
|
+
open: "outline",
|
|
860
|
+
void: "destructive"
|
|
861
|
+
};
|
|
862
|
+
function BillingPanel({
|
|
863
|
+
planName,
|
|
864
|
+
planPrice,
|
|
865
|
+
renewalDate,
|
|
866
|
+
usage,
|
|
867
|
+
paymentMethod,
|
|
868
|
+
invoices,
|
|
869
|
+
onManagePlan,
|
|
870
|
+
onUpdatePaymentMethod,
|
|
871
|
+
className
|
|
872
|
+
}) {
|
|
873
|
+
const invoiceColumns = React30.useMemo(() => [
|
|
874
|
+
{ accessorKey: "date", header: "Date" },
|
|
875
|
+
{ accessorKey: "amount", header: "Amount" },
|
|
876
|
+
{
|
|
877
|
+
accessorKey: "status",
|
|
878
|
+
header: "Status",
|
|
879
|
+
cell: ({ row }) => /* @__PURE__ */ jsx(Badge$1, { variant: invoiceStatusVariant[row.original.status], className: "capitalize", children: row.original.status })
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
id: "download",
|
|
883
|
+
header: "",
|
|
884
|
+
cell: ({ row }) => row.original.downloadUrl ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", className: "size-8", asChild: true, children: /* @__PURE__ */ jsxs("a", { href: row.original.downloadUrl, download: true, children: [
|
|
885
|
+
/* @__PURE__ */ jsx(Download, { className: "size-4" }),
|
|
886
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Download invoice" })
|
|
887
|
+
] }) }) : null,
|
|
888
|
+
enableSorting: false
|
|
889
|
+
}
|
|
890
|
+
], []);
|
|
891
|
+
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs("div", { className: "divide-y divide-border", children: [
|
|
892
|
+
/* @__PURE__ */ jsxs(SettingsSection, { title: "Current plan", description: "Your subscription and usage against plan limits.", children: [
|
|
893
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 rounded-lg border bg-card/60 p-4", children: [
|
|
894
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
895
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
896
|
+
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold", children: planName }),
|
|
897
|
+
planPrice && /* @__PURE__ */ jsx(Badge$1, { variant: "secondary", children: planPrice })
|
|
898
|
+
] }),
|
|
899
|
+
renewalDate && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-muted-foreground", children: [
|
|
900
|
+
"Renews ",
|
|
901
|
+
renewalDate
|
|
902
|
+
] })
|
|
903
|
+
] }),
|
|
904
|
+
onManagePlan && /* @__PURE__ */ jsx(Button, { onClick: onManagePlan, children: "Manage plan" })
|
|
905
|
+
] }),
|
|
906
|
+
usage && usage.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-4", children: usage.map((metric) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
907
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", children: [
|
|
908
|
+
/* @__PURE__ */ jsx("span", { children: metric.label }),
|
|
909
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
910
|
+
metric.used,
|
|
911
|
+
metric.unit,
|
|
912
|
+
" / ",
|
|
913
|
+
metric.limit,
|
|
914
|
+
metric.unit
|
|
915
|
+
] })
|
|
916
|
+
] }),
|
|
917
|
+
/* @__PURE__ */ jsx(Progress, { value: metric.used / metric.limit * 100 })
|
|
918
|
+
] }, metric.label)) })
|
|
919
|
+
] }),
|
|
920
|
+
paymentMethod && /* @__PURE__ */ jsx(SettingsSection, { title: "Payment method", description: "Used for your subscription renewal.", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 rounded-lg border bg-card/60 p-4", children: [
|
|
921
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
922
|
+
/* @__PURE__ */ jsx(CreditCard, { className: "size-5 text-muted-foreground" }),
|
|
923
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
|
|
924
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium capitalize", children: paymentMethod.brand }),
|
|
925
|
+
" ending in ",
|
|
926
|
+
paymentMethod.last4,
|
|
927
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
928
|
+
" \xB7 expires ",
|
|
929
|
+
paymentMethod.expiry
|
|
930
|
+
] })
|
|
931
|
+
] })
|
|
932
|
+
] }),
|
|
933
|
+
onUpdatePaymentMethod && /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onUpdatePaymentMethod, children: "Update" })
|
|
934
|
+
] }) }),
|
|
935
|
+
invoices && invoices.length > 0 && /* @__PURE__ */ jsx(SettingsSection, { title: "Billing history", description: "Download past invoices for your records.", children: /* @__PURE__ */ jsx(DataTable, { columns: invoiceColumns, data: invoices, pageSize: 5 }) })
|
|
936
|
+
] }) });
|
|
937
|
+
}
|
|
938
|
+
function OnboardingWizard({
|
|
939
|
+
steps,
|
|
940
|
+
onComplete,
|
|
941
|
+
onStepChange,
|
|
942
|
+
step: stepProp,
|
|
943
|
+
onStepIndexChange,
|
|
944
|
+
completeLabel = "Finish",
|
|
945
|
+
className
|
|
946
|
+
}) {
|
|
947
|
+
const [internalStep, setInternalStep] = React30.useState(0);
|
|
948
|
+
const [error, setError] = React30.useState(null);
|
|
949
|
+
const [pending, setPending] = React30.useState(false);
|
|
950
|
+
const index = stepProp ?? internalStep;
|
|
951
|
+
const current = steps[index];
|
|
952
|
+
const isLast = index === steps.length - 1;
|
|
953
|
+
const goTo = (next) => {
|
|
954
|
+
setError(null);
|
|
955
|
+
if (stepProp === void 0) setInternalStep(next);
|
|
956
|
+
onStepIndexChange?.(next);
|
|
957
|
+
onStepChange?.(next);
|
|
958
|
+
};
|
|
959
|
+
const handleNext = async () => {
|
|
960
|
+
if (current.onNext) {
|
|
961
|
+
setPending(true);
|
|
962
|
+
const result = await current.onNext();
|
|
963
|
+
setPending(false);
|
|
964
|
+
if (result === false) {
|
|
965
|
+
setError("Please complete this step before continuing.");
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (typeof result === "string") {
|
|
969
|
+
setError(result);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (isLast) {
|
|
974
|
+
onComplete?.();
|
|
975
|
+
} else {
|
|
976
|
+
goTo(index + 1);
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
const handleBack = () => {
|
|
980
|
+
if (index > 0) goTo(index - 1);
|
|
981
|
+
};
|
|
982
|
+
return /* @__PURE__ */ jsxs("div", { className: cn$1("w-full max-w-lg space-y-8", className), children: [
|
|
983
|
+
/* @__PURE__ */ jsx("ol", { className: "flex items-center", children: steps.map((step, i) => /* @__PURE__ */ jsxs("li", { className: cn$1("flex items-center", i !== steps.length - 1 && "flex-1"), children: [
|
|
984
|
+
/* @__PURE__ */ jsx(
|
|
985
|
+
"div",
|
|
986
|
+
{
|
|
987
|
+
className: cn$1(
|
|
988
|
+
"flex size-8 shrink-0 items-center justify-center rounded-full border text-xs font-medium",
|
|
989
|
+
i < index ? "border-primary bg-primary text-primary-foreground" : i === index ? "border-primary text-primary" : "border-border text-muted-foreground"
|
|
990
|
+
),
|
|
991
|
+
children: i < index ? /* @__PURE__ */ jsx(Check, { className: "size-4" }) : i + 1
|
|
992
|
+
}
|
|
993
|
+
),
|
|
994
|
+
i !== steps.length - 1 && /* @__PURE__ */ jsx("div", { className: cn$1("mx-2 h-px flex-1", i < index ? "bg-primary" : "bg-border") })
|
|
995
|
+
] }, step.id)) }),
|
|
996
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
997
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
998
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold tracking-tight", children: current.title }),
|
|
999
|
+
current.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: current.description })
|
|
1000
|
+
] }),
|
|
1001
|
+
/* @__PURE__ */ jsx("div", { children: current.content }),
|
|
1002
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: error })
|
|
1003
|
+
] }),
|
|
1004
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1005
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: handleBack, disabled: index === 0 || pending, children: "Back" }),
|
|
1006
|
+
/* @__PURE__ */ jsx(Button, { type: "button", onClick: handleNext, disabled: pending, children: pending ? "Please wait\u2026" : isLast ? completeLabel : "Continue" })
|
|
1007
|
+
] })
|
|
1008
|
+
] });
|
|
1009
|
+
}
|
|
587
1010
|
function EmptyState({ icon: Icon, title, description, action, className, ...props }) {
|
|
588
1011
|
return /* @__PURE__ */ jsxs(
|
|
589
1012
|
"div",
|
|
@@ -833,9 +1256,9 @@ function FadeIn({
|
|
|
833
1256
|
style,
|
|
834
1257
|
...props
|
|
835
1258
|
}) {
|
|
836
|
-
const ref =
|
|
837
|
-
const [visible, setVisible] =
|
|
838
|
-
|
|
1259
|
+
const ref = React30.useRef(null);
|
|
1260
|
+
const [visible, setVisible] = React30.useState(false);
|
|
1261
|
+
React30.useEffect(() => {
|
|
839
1262
|
const el = ref.current;
|
|
840
1263
|
if (!el) return;
|
|
841
1264
|
const observer = new IntersectionObserver(
|
|
@@ -1038,9 +1461,9 @@ function StaggerChildren({
|
|
|
1038
1461
|
className,
|
|
1039
1462
|
...props
|
|
1040
1463
|
}) {
|
|
1041
|
-
const ref =
|
|
1042
|
-
const [visible, setVisible] =
|
|
1043
|
-
|
|
1464
|
+
const ref = React30.useRef(null);
|
|
1465
|
+
const [visible, setVisible] = React30.useState(false);
|
|
1466
|
+
React30.useEffect(() => {
|
|
1044
1467
|
const el = ref.current;
|
|
1045
1468
|
if (!el) return;
|
|
1046
1469
|
const observer = new IntersectionObserver(
|
|
@@ -1058,7 +1481,7 @@ function StaggerChildren({
|
|
|
1058
1481
|
return () => observer.disconnect();
|
|
1059
1482
|
}, [once]);
|
|
1060
1483
|
const [tx, ty] = translateMap2[direction];
|
|
1061
|
-
return /* @__PURE__ */ jsx("div", { ref, className: cn$1(className), ...props, children:
|
|
1484
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn$1(className), ...props, children: React30.Children.map(children, (child, i) => /* @__PURE__ */ jsx(
|
|
1062
1485
|
"div",
|
|
1063
1486
|
{
|
|
1064
1487
|
className: "h-full",
|
|
@@ -1233,16 +1656,16 @@ function CarouselSection({
|
|
|
1233
1656
|
features
|
|
1234
1657
|
}) {
|
|
1235
1658
|
const mode = useUIVariant();
|
|
1236
|
-
const trackRef =
|
|
1237
|
-
const [canPrev, setCanPrev] =
|
|
1238
|
-
const [canNext, setCanNext] =
|
|
1239
|
-
const updateScrollState =
|
|
1659
|
+
const trackRef = React30.useRef(null);
|
|
1660
|
+
const [canPrev, setCanPrev] = React30.useState(false);
|
|
1661
|
+
const [canNext, setCanNext] = React30.useState(true);
|
|
1662
|
+
const updateScrollState = React30.useCallback(() => {
|
|
1240
1663
|
const track = trackRef.current;
|
|
1241
1664
|
if (!track) return;
|
|
1242
1665
|
setCanPrev(track.scrollLeft > 8);
|
|
1243
1666
|
setCanNext(track.scrollLeft < track.scrollWidth - track.clientWidth - 8);
|
|
1244
1667
|
}, []);
|
|
1245
|
-
|
|
1668
|
+
React30.useEffect(() => {
|
|
1246
1669
|
const track = trackRef.current;
|
|
1247
1670
|
if (!track) return;
|
|
1248
1671
|
updateScrollState();
|
|
@@ -1454,9 +1877,9 @@ function CtaCardSection({
|
|
|
1454
1877
|
icon,
|
|
1455
1878
|
renderLink = defaultRenderLink6
|
|
1456
1879
|
}) {
|
|
1457
|
-
const ref =
|
|
1458
|
-
const [visible, setVisible] =
|
|
1459
|
-
|
|
1880
|
+
const ref = React30.useRef(null);
|
|
1881
|
+
const [visible, setVisible] = React30.useState(false);
|
|
1882
|
+
React30.useEffect(() => {
|
|
1460
1883
|
const el = ref.current;
|
|
1461
1884
|
if (!el) return;
|
|
1462
1885
|
const observer = new IntersectionObserver(
|
|
@@ -1603,8 +2026,8 @@ function CountdownTimer({
|
|
|
1603
2026
|
className,
|
|
1604
2027
|
compact = false
|
|
1605
2028
|
}) {
|
|
1606
|
-
const [timeLeft, setTimeLeft] =
|
|
1607
|
-
|
|
2029
|
+
const [timeLeft, setTimeLeft] = React30.useState(() => getTimeLeft(deadline));
|
|
2030
|
+
React30.useEffect(() => {
|
|
1608
2031
|
setTimeLeft(getTimeLeft(deadline));
|
|
1609
2032
|
const id = setInterval(() => setTimeLeft(getTimeLeft(deadline)), 1e3);
|
|
1610
2033
|
return () => clearInterval(id);
|
|
@@ -1630,7 +2053,7 @@ function CountdownTimer({
|
|
|
1630
2053
|
];
|
|
1631
2054
|
return /* @__PURE__ */ jsxs("div", { className: cn$1("flex items-center justify-center gap-2 text-sm", className), children: [
|
|
1632
2055
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
|
|
1633
|
-
/* @__PURE__ */ jsx("span", { className: "flex items-center gap-1 font-mono font-semibold tabular-nums", children: units.map(({ value, unit }, i) => /* @__PURE__ */ jsxs(
|
|
2056
|
+
/* @__PURE__ */ jsx("span", { className: "flex items-center gap-1 font-mono font-semibold tabular-nums", children: units.map(({ value, unit }, i) => /* @__PURE__ */ jsxs(React30.Fragment, { children: [
|
|
1634
2057
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
1635
2058
|
String(value).padStart(2, "0"),
|
|
1636
2059
|
/* @__PURE__ */ jsx("span", { className: "font-normal text-muted-foreground", children: unit })
|
|
@@ -1653,7 +2076,7 @@ function PricingSection({
|
|
|
1653
2076
|
foundingDeadline,
|
|
1654
2077
|
renderPlanFooter
|
|
1655
2078
|
}) {
|
|
1656
|
-
const [annual, setAnnual] =
|
|
2079
|
+
const [annual, setAnnual] = React30.useState(false);
|
|
1657
2080
|
const isOneTime = mode === "one-time";
|
|
1658
2081
|
const uiMode = useUIVariant();
|
|
1659
2082
|
const sectionClasses = cn$1(
|
|
@@ -1915,10 +2338,10 @@ function CountUp({
|
|
|
1915
2338
|
className,
|
|
1916
2339
|
...props
|
|
1917
2340
|
}) {
|
|
1918
|
-
const ref =
|
|
1919
|
-
const [value, setValue] =
|
|
1920
|
-
const hasStarted =
|
|
1921
|
-
|
|
2341
|
+
const ref = React30.useRef(null);
|
|
2342
|
+
const [value, setValue] = React30.useState(from);
|
|
2343
|
+
const hasStarted = React30.useRef(false);
|
|
2344
|
+
React30.useEffect(() => {
|
|
1922
2345
|
const el = ref.current;
|
|
1923
2346
|
if (!el) return;
|
|
1924
2347
|
const observer = new IntersectionObserver(
|
|
@@ -2011,8 +2434,8 @@ function NewsletterSection({
|
|
|
2011
2434
|
successDescription = defaults.successDescription,
|
|
2012
2435
|
onSubscribe
|
|
2013
2436
|
} = {}) {
|
|
2014
|
-
const [email, setEmail] =
|
|
2015
|
-
const [submitted, setSubmitted] =
|
|
2437
|
+
const [email, setEmail] = React30.useState("");
|
|
2438
|
+
const [submitted, setSubmitted] = React30.useState(false);
|
|
2016
2439
|
async function handleSubmit(e) {
|
|
2017
2440
|
e.preventDefault();
|
|
2018
2441
|
if (!email) return;
|
|
@@ -2096,7 +2519,7 @@ function ContactSection({
|
|
|
2096
2519
|
sendAnotherLabel = defaults2.sendAnotherLabel,
|
|
2097
2520
|
onSubmit
|
|
2098
2521
|
} = {}) {
|
|
2099
|
-
const [submitted, setSubmitted] =
|
|
2522
|
+
const [submitted, setSubmitted] = React30.useState(false);
|
|
2100
2523
|
async function handleSubmit(e) {
|
|
2101
2524
|
e.preventDefault();
|
|
2102
2525
|
await onSubmit?.(e);
|
|
@@ -2387,7 +2810,7 @@ function Navbar({
|
|
|
2387
2810
|
controls,
|
|
2388
2811
|
renderLink = defaultRenderLink8
|
|
2389
2812
|
}) {
|
|
2390
|
-
const [open, setOpen] =
|
|
2813
|
+
const [open, setOpen] = React30.useState(false);
|
|
2391
2814
|
const brandHref = brand.href ?? "/";
|
|
2392
2815
|
const hasRightContent = controls?.length || cta?.primary || cta?.secondary;
|
|
2393
2816
|
return /* @__PURE__ */ jsx("section", { className: "overflow-hidden rounded-2xl border bg-card", children: /* @__PURE__ */ jsxs("nav", { className: "grid h-16 grid-cols-[1fr_auto_1fr] items-center px-6", children: [
|
|
@@ -2404,7 +2827,7 @@ function Navbar({
|
|
|
2404
2827
|
children: link.label
|
|
2405
2828
|
}) }, link.label)) }),
|
|
2406
2829
|
hasRightContent && /* @__PURE__ */ jsxs("div", { className: "hidden items-center justify-end gap-2 md:flex", children: [
|
|
2407
|
-
controls?.map((control, i) => /* @__PURE__ */ jsx(
|
|
2830
|
+
controls?.map((control, i) => /* @__PURE__ */ jsx(React30.Fragment, { children: control }, i)),
|
|
2408
2831
|
(cta?.secondary || cta?.primary) && controls?.length ? /* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-border" }) : null,
|
|
2409
2832
|
cta?.secondary && renderLink({
|
|
2410
2833
|
href: cta.secondary.href,
|
|
@@ -2428,7 +2851,7 @@ function Navbar({
|
|
|
2428
2851
|
/* @__PURE__ */ jsx("span", { children: brand.name })
|
|
2429
2852
|
] })
|
|
2430
2853
|
}) }) }),
|
|
2431
|
-
controls?.length ? /* @__PURE__ */ jsx("div", { className: "mb-4 flex w-full items-center justify-between gap-1", children: controls.map((control, i) => /* @__PURE__ */ jsx(
|
|
2854
|
+
controls?.length ? /* @__PURE__ */ jsx("div", { className: "mb-4 flex w-full items-center justify-between gap-1", children: controls.map((control, i) => /* @__PURE__ */ jsx(React30.Fragment, { children: control }, i)) }) : null,
|
|
2432
2855
|
/* @__PURE__ */ jsx(Separator, {}),
|
|
2433
2856
|
/* @__PURE__ */ jsx("nav", { className: "mt-4 flex flex-col gap-1", children: navLinks.map((link) => /* @__PURE__ */ jsx("span", { onClick: () => setOpen(false), children: renderLink({
|
|
2434
2857
|
href: link.href,
|
|
@@ -2577,7 +3000,7 @@ function LogoStrip({
|
|
|
2577
3000
|
] });
|
|
2578
3001
|
}
|
|
2579
3002
|
function GridUnderlay({ variant, size = 24, className, ...props }) {
|
|
2580
|
-
const id =
|
|
3003
|
+
const id = React30.useId();
|
|
2581
3004
|
const pattern = variant === "dots" ? /* @__PURE__ */ jsx("circle", { cx: size / 2, cy: size / 2, r: 1, fill: "currentColor" }) : variant === "lines" ? /* @__PURE__ */ jsx("path", { d: `M ${size} 0 L 0 0 0 ${size}`, fill: "none", stroke: "currentColor", strokeWidth: 0.5 }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2582
3005
|
/* @__PURE__ */ jsx("line", { x1: size / 2, y1: 0, x2: size / 2, y2: size, stroke: "currentColor", strokeWidth: 0.5 }),
|
|
2583
3006
|
/* @__PURE__ */ jsx("line", { x1: 0, y1: size / 2, x2: size, y2: size / 2, stroke: "currentColor", strokeWidth: 0.5 })
|
|
@@ -2605,9 +3028,9 @@ function GlowUnderlay({
|
|
|
2605
3028
|
style,
|
|
2606
3029
|
...props
|
|
2607
3030
|
}) {
|
|
2608
|
-
const [pos, setPos] =
|
|
2609
|
-
const containerRef =
|
|
2610
|
-
|
|
3031
|
+
const [pos, setPos] = React30.useState({ x: "50%", y: "50%" });
|
|
3032
|
+
const containerRef = React30.useRef(null);
|
|
3033
|
+
React30.useEffect(() => {
|
|
2611
3034
|
if (!followCursor) return;
|
|
2612
3035
|
const handleMouseMove = (e) => {
|
|
2613
3036
|
const el = containerRef.current;
|
|
@@ -2746,8 +3169,8 @@ function PageTransition({
|
|
|
2746
3169
|
style,
|
|
2747
3170
|
...props
|
|
2748
3171
|
}) {
|
|
2749
|
-
const [mounted, setMounted] =
|
|
2750
|
-
|
|
3172
|
+
const [mounted, setMounted] = React30.useState(false);
|
|
3173
|
+
React30.useEffect(() => {
|
|
2751
3174
|
const id = requestAnimationFrame(() => setMounted(true));
|
|
2752
3175
|
return () => cancelAnimationFrame(id);
|
|
2753
3176
|
}, []);
|
|
@@ -2774,8 +3197,8 @@ function Spotlight({
|
|
|
2774
3197
|
triggerClassName,
|
|
2775
3198
|
shortcut = "\u2318K"
|
|
2776
3199
|
}) {
|
|
2777
|
-
const [open, setOpen] =
|
|
2778
|
-
|
|
3200
|
+
const [open, setOpen] = React30.useState(false);
|
|
3201
|
+
React30.useEffect(() => {
|
|
2779
3202
|
const down = (e) => {
|
|
2780
3203
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
2781
3204
|
e.preventDefault();
|
|
@@ -2805,7 +3228,7 @@ function Spotlight({
|
|
|
2805
3228
|
/* @__PURE__ */ jsx(CommandInput, { placeholder }),
|
|
2806
3229
|
/* @__PURE__ */ jsxs(CommandList, { children: [
|
|
2807
3230
|
/* @__PURE__ */ jsx(CommandEmpty, { children: "No results found." }),
|
|
2808
|
-
groups.map((group, i) => /* @__PURE__ */ jsxs(
|
|
3231
|
+
groups.map((group, i) => /* @__PURE__ */ jsxs(React30.Fragment, { children: [
|
|
2809
3232
|
i > 0 && /* @__PURE__ */ jsx(CommandSeparator, {}),
|
|
2810
3233
|
/* @__PURE__ */ jsx(CommandGroup, { heading: group.heading, children: group.items.map((item) => /* @__PURE__ */ jsxs(
|
|
2811
3234
|
CommandItem,
|
|
@@ -2830,8 +3253,8 @@ function Spotlight({
|
|
|
2830
3253
|
var BASE = 40;
|
|
2831
3254
|
var MAGNIFY_RADIUS = 80;
|
|
2832
3255
|
function Dock({ items, iconSize = BASE, magnification = 1.7, className, ...props }) {
|
|
2833
|
-
const [mouseX, setMouseX] =
|
|
2834
|
-
const containerRef =
|
|
3256
|
+
const [mouseX, setMouseX] = React30.useState(null);
|
|
3257
|
+
const containerRef = React30.useRef(null);
|
|
2835
3258
|
function getScale(index) {
|
|
2836
3259
|
if (mouseX === null || !containerRef.current) return 1;
|
|
2837
3260
|
const container = containerRef.current;
|
|
@@ -2929,6 +3352,274 @@ function ConfirmDialog({ isOpen, options, handleConfirm, handleCancel, destructi
|
|
|
2929
3352
|
] })
|
|
2930
3353
|
] }) });
|
|
2931
3354
|
}
|
|
3355
|
+
function RegisterHotkeys({ hotkeys }) {
|
|
3356
|
+
React30.useEffect(() => {
|
|
3357
|
+
const handleKeyDown = (event) => {
|
|
3358
|
+
for (const hotkey of hotkeys) {
|
|
3359
|
+
const matches = event.key.toLowerCase() === hotkey.key.toLowerCase() && !!event.ctrlKey === !!hotkey.ctrl && !!event.metaKey === !!hotkey.meta && !!event.shiftKey === !!hotkey.shift && !!event.altKey === !!hotkey.alt;
|
|
3360
|
+
if (matches) {
|
|
3361
|
+
event.preventDefault();
|
|
3362
|
+
hotkey.handler();
|
|
3363
|
+
break;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
};
|
|
3367
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3368
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
3369
|
+
}, [hotkeys]);
|
|
3370
|
+
return null;
|
|
3371
|
+
}
|
|
3372
|
+
function useControlledOpen(defaultOpen = false) {
|
|
3373
|
+
const [isOpen, setIsOpen] = React30.useState(defaultOpen);
|
|
3374
|
+
const open = React30.useCallback(() => setIsOpen(true), []);
|
|
3375
|
+
const close = React30.useCallback(() => setIsOpen(false), []);
|
|
3376
|
+
const toggle = React30.useCallback(() => setIsOpen((prev) => !prev), []);
|
|
3377
|
+
return { isOpen, open, close, toggle, setIsOpen };
|
|
3378
|
+
}
|
|
3379
|
+
function CommandMenu({
|
|
3380
|
+
groups,
|
|
3381
|
+
placeholder = "Type a command or search\u2026",
|
|
3382
|
+
emptyMessage = "No results found.",
|
|
3383
|
+
open: openProp,
|
|
3384
|
+
onOpenChange,
|
|
3385
|
+
hotkey = true
|
|
3386
|
+
}) {
|
|
3387
|
+
const internal = useControlledOpen(false);
|
|
3388
|
+
const isOpen = openProp ?? internal.isOpen;
|
|
3389
|
+
const setOpen = onOpenChange ?? internal.setIsOpen;
|
|
3390
|
+
const runItem = (item) => {
|
|
3391
|
+
setOpen(false);
|
|
3392
|
+
item.onSelect();
|
|
3393
|
+
};
|
|
3394
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3395
|
+
hotkey && /* @__PURE__ */ jsx(
|
|
3396
|
+
RegisterHotkeys,
|
|
3397
|
+
{
|
|
3398
|
+
hotkeys: [
|
|
3399
|
+
{ key: "k", meta: true, handler: () => setOpen(!isOpen) },
|
|
3400
|
+
{ key: "k", ctrl: true, handler: () => setOpen(!isOpen) }
|
|
3401
|
+
]
|
|
3402
|
+
}
|
|
3403
|
+
),
|
|
3404
|
+
/* @__PURE__ */ jsxs(CommandDialog, { open: isOpen, onOpenChange: setOpen, children: [
|
|
3405
|
+
/* @__PURE__ */ jsx(CommandInput, { placeholder }),
|
|
3406
|
+
/* @__PURE__ */ jsxs(CommandList, { children: [
|
|
3407
|
+
/* @__PURE__ */ jsx(CommandEmpty, { children: emptyMessage }),
|
|
3408
|
+
groups.map((group, i) => /* @__PURE__ */ jsxs(React30.Fragment, { children: [
|
|
3409
|
+
i > 0 && /* @__PURE__ */ jsx(CommandSeparator, {}),
|
|
3410
|
+
/* @__PURE__ */ jsx(CommandGroup, { heading: group.heading, children: group.items.map((item) => /* @__PURE__ */ jsxs(
|
|
3411
|
+
CommandItem,
|
|
3412
|
+
{
|
|
3413
|
+
value: [item.label, ...item.keywords ?? []].join(" "),
|
|
3414
|
+
onSelect: () => runItem(item),
|
|
3415
|
+
children: [
|
|
3416
|
+
item.icon && /* @__PURE__ */ jsx(item.icon, {}),
|
|
3417
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
3418
|
+
item.shortcut && /* @__PURE__ */ jsx(CommandShortcut, { children: item.shortcut })
|
|
3419
|
+
]
|
|
3420
|
+
},
|
|
3421
|
+
item.id
|
|
3422
|
+
)) })
|
|
3423
|
+
] }, group.heading))
|
|
3424
|
+
] })
|
|
3425
|
+
] })
|
|
3426
|
+
] });
|
|
3427
|
+
}
|
|
3428
|
+
function formatBytes(bytes) {
|
|
3429
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3430
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
3431
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3432
|
+
}
|
|
3433
|
+
function makeId() {
|
|
3434
|
+
return Math.random().toString(36).slice(2, 10);
|
|
3435
|
+
}
|
|
3436
|
+
function FileUpload({
|
|
3437
|
+
accept,
|
|
3438
|
+
multiple = false,
|
|
3439
|
+
maxSizeMb,
|
|
3440
|
+
maxFiles,
|
|
3441
|
+
files: filesProp,
|
|
3442
|
+
defaultFiles,
|
|
3443
|
+
onFilesChange,
|
|
3444
|
+
onFilesAdded,
|
|
3445
|
+
disabled,
|
|
3446
|
+
hint,
|
|
3447
|
+
className
|
|
3448
|
+
}) {
|
|
3449
|
+
const [internalFiles, setInternalFiles] = React30.useState(defaultFiles ?? []);
|
|
3450
|
+
const [isDragging, setIsDragging] = React30.useState(false);
|
|
3451
|
+
const [validationError, setValidationError] = React30.useState(null);
|
|
3452
|
+
const inputRef = React30.useRef(null);
|
|
3453
|
+
const files = filesProp ?? internalFiles;
|
|
3454
|
+
const setFiles = React30.useCallback(
|
|
3455
|
+
(next) => {
|
|
3456
|
+
if (!filesProp) setInternalFiles(next);
|
|
3457
|
+
onFilesChange?.(next);
|
|
3458
|
+
},
|
|
3459
|
+
[filesProp, onFilesChange]
|
|
3460
|
+
);
|
|
3461
|
+
const acceptList = React30.useMemo(
|
|
3462
|
+
() => accept?.split(",").map((a) => a.trim().toLowerCase()).filter(Boolean) ?? [],
|
|
3463
|
+
[accept]
|
|
3464
|
+
);
|
|
3465
|
+
const matchesAccept = (file) => {
|
|
3466
|
+
if (!acceptList.length) return true;
|
|
3467
|
+
const name = file.name.toLowerCase();
|
|
3468
|
+
return acceptList.some(
|
|
3469
|
+
(pattern) => pattern.startsWith(".") ? name.endsWith(pattern) : file.type === pattern || file.type.startsWith(pattern.replace("/*", "/"))
|
|
3470
|
+
);
|
|
3471
|
+
};
|
|
3472
|
+
const handleFiles = (fileList) => {
|
|
3473
|
+
if (!fileList || disabled) return;
|
|
3474
|
+
const incoming = Array.from(fileList);
|
|
3475
|
+
const room = maxFiles ? Math.max(0, maxFiles - files.length) : Infinity;
|
|
3476
|
+
if (maxFiles && room <= 0) {
|
|
3477
|
+
setValidationError(`You can only add up to ${maxFiles} file${maxFiles === 1 ? "" : "s"}.`);
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
const accepted = [];
|
|
3481
|
+
let rejected = false;
|
|
3482
|
+
for (const file of incoming.slice(0, room)) {
|
|
3483
|
+
if (!matchesAccept(file)) {
|
|
3484
|
+
rejected = true;
|
|
3485
|
+
continue;
|
|
3486
|
+
}
|
|
3487
|
+
if (maxSizeMb && file.size > maxSizeMb * 1024 * 1024) {
|
|
3488
|
+
rejected = true;
|
|
3489
|
+
continue;
|
|
3490
|
+
}
|
|
3491
|
+
accepted.push(file);
|
|
3492
|
+
}
|
|
3493
|
+
setValidationError(rejected ? `Some files were skipped \u2014 check the file type and size limit.` : null);
|
|
3494
|
+
if (!accepted.length) return;
|
|
3495
|
+
const entries = accepted.map((file) => ({ id: makeId(), file, status: "pending" }));
|
|
3496
|
+
setFiles(multiple ? [...files, ...entries] : entries);
|
|
3497
|
+
onFilesAdded?.(accepted);
|
|
3498
|
+
};
|
|
3499
|
+
const removeFile = (id) => {
|
|
3500
|
+
setFiles(files.filter((f) => f.id !== id));
|
|
3501
|
+
};
|
|
3502
|
+
return /* @__PURE__ */ jsxs("div", { className: cn$1("space-y-3", className), children: [
|
|
3503
|
+
/* @__PURE__ */ jsxs(
|
|
3504
|
+
"div",
|
|
3505
|
+
{
|
|
3506
|
+
role: "button",
|
|
3507
|
+
tabIndex: disabled ? -1 : 0,
|
|
3508
|
+
onClick: () => !disabled && inputRef.current?.click(),
|
|
3509
|
+
onKeyDown: (e) => {
|
|
3510
|
+
if (e.key === "Enter" || e.key === " ") inputRef.current?.click();
|
|
3511
|
+
},
|
|
3512
|
+
onDragOver: (e) => {
|
|
3513
|
+
e.preventDefault();
|
|
3514
|
+
if (!disabled) setIsDragging(true);
|
|
3515
|
+
},
|
|
3516
|
+
onDragLeave: () => setIsDragging(false),
|
|
3517
|
+
onDrop: (e) => {
|
|
3518
|
+
e.preventDefault();
|
|
3519
|
+
setIsDragging(false);
|
|
3520
|
+
handleFiles(e.dataTransfer.files);
|
|
3521
|
+
},
|
|
3522
|
+
className: cn$1(
|
|
3523
|
+
"flex cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed px-6 py-10 text-center transition-colors",
|
|
3524
|
+
isDragging ? "border-primary bg-primary/5" : "border-border hover:border-foreground/30",
|
|
3525
|
+
disabled && "pointer-events-none opacity-50"
|
|
3526
|
+
),
|
|
3527
|
+
children: [
|
|
3528
|
+
/* @__PURE__ */ jsx(UploadCloud, { className: "size-8 text-muted-foreground" }),
|
|
3529
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
|
|
3530
|
+
/* @__PURE__ */ jsx("span", { className: "text-primary underline underline-offset-4", children: "Click to upload" }),
|
|
3531
|
+
" or drag and drop"
|
|
3532
|
+
] }),
|
|
3533
|
+
hint && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hint }),
|
|
3534
|
+
/* @__PURE__ */ jsx(
|
|
3535
|
+
"input",
|
|
3536
|
+
{
|
|
3537
|
+
ref: inputRef,
|
|
3538
|
+
type: "file",
|
|
3539
|
+
accept,
|
|
3540
|
+
multiple,
|
|
3541
|
+
disabled,
|
|
3542
|
+
className: "sr-only",
|
|
3543
|
+
onChange: (e) => {
|
|
3544
|
+
handleFiles(e.target.files);
|
|
3545
|
+
e.target.value = "";
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
)
|
|
3549
|
+
]
|
|
3550
|
+
}
|
|
3551
|
+
),
|
|
3552
|
+
validationError && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: validationError }),
|
|
3553
|
+
files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: files.map(({ id, file, progress, status = "pending", error }) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-lg border bg-card/60 px-3 py-2.5", children: [
|
|
3554
|
+
/* @__PURE__ */ jsx(File, { className: "size-5 shrink-0 text-muted-foreground" }),
|
|
3555
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3556
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
3557
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: file.name }),
|
|
3558
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatBytes(file.size) })
|
|
3559
|
+
] }),
|
|
3560
|
+
status === "uploading" && /* @__PURE__ */ jsx("div", { className: "mt-1.5 h-1 w-full overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx("div", { className: "h-full rounded-full bg-primary transition-all", style: { width: `${progress ?? 0}%` } }) }),
|
|
3561
|
+
status === "error" && error && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: error })
|
|
3562
|
+
] }),
|
|
3563
|
+
status === "uploading" && /* @__PURE__ */ jsx(Loader2, { className: "size-4 shrink-0 animate-spin text-muted-foreground" }),
|
|
3564
|
+
status === "done" && /* @__PURE__ */ jsx(CheckCircle2, { className: "size-4 shrink-0 text-primary" }),
|
|
3565
|
+
status === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "size-4 shrink-0 text-destructive" }),
|
|
3566
|
+
/* @__PURE__ */ jsxs(
|
|
3567
|
+
Button,
|
|
3568
|
+
{
|
|
3569
|
+
type: "button",
|
|
3570
|
+
variant: "ghost",
|
|
3571
|
+
size: "icon",
|
|
3572
|
+
className: "size-7 shrink-0",
|
|
3573
|
+
onClick: () => removeFile(id),
|
|
3574
|
+
children: [
|
|
3575
|
+
/* @__PURE__ */ jsx(X, { className: "size-3.5" }),
|
|
3576
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
3577
|
+
"Remove ",
|
|
3578
|
+
file.name
|
|
3579
|
+
] })
|
|
3580
|
+
]
|
|
3581
|
+
}
|
|
3582
|
+
)
|
|
3583
|
+
] }, id)) })
|
|
3584
|
+
] });
|
|
3585
|
+
}
|
|
3586
|
+
var variantIcon = {
|
|
3587
|
+
success: /* @__PURE__ */ jsx(CheckCircle2, { className: "size-5 text-primary" }),
|
|
3588
|
+
info: /* @__PURE__ */ jsx(Info, { className: "size-5 text-muted-foreground" }),
|
|
3589
|
+
message: /* @__PURE__ */ jsx(Inbox, { className: "size-5 text-muted-foreground" })
|
|
3590
|
+
};
|
|
3591
|
+
function NotificationToast({
|
|
3592
|
+
variant = "info",
|
|
3593
|
+
title,
|
|
3594
|
+
description,
|
|
3595
|
+
avatar,
|
|
3596
|
+
action,
|
|
3597
|
+
secondaryAction,
|
|
3598
|
+
onDismiss
|
|
3599
|
+
}) {
|
|
3600
|
+
return /* @__PURE__ */ jsx("div", { className: "w-full max-w-sm rounded-lg border bg-card p-4 text-card-foreground shadow-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
3601
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 pt-0.5", children: avatar ? (
|
|
3602
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
3603
|
+
/* @__PURE__ */ jsx("img", { src: avatar, alt: "", className: "size-9 rounded-full border object-cover" })
|
|
3604
|
+
) : variantIcon[variant] }),
|
|
3605
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 pt-0.5", children: [
|
|
3606
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: title }),
|
|
3607
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description }),
|
|
3608
|
+
(action || secondaryAction) && /* @__PURE__ */ jsxs("div", { className: cn$1("mt-3 flex gap-4", avatar && "gap-3"), children: [
|
|
3609
|
+
action && /* @__PURE__ */ jsx("button", { type: "button", onClick: action.onClick, className: "text-sm font-medium text-primary hover:underline", children: action.label }),
|
|
3610
|
+
secondaryAction && /* @__PURE__ */ jsx("button", { type: "button", onClick: secondaryAction.onClick, className: "text-sm font-medium text-muted-foreground hover:text-foreground", children: secondaryAction.label })
|
|
3611
|
+
] })
|
|
3612
|
+
] }),
|
|
3613
|
+
/* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "icon", className: "size-6 shrink-0 -mt-1 -mr-1", onClick: onDismiss, children: [
|
|
3614
|
+
/* @__PURE__ */ jsx(X, { className: "size-4" }),
|
|
3615
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Dismiss" })
|
|
3616
|
+
] })
|
|
3617
|
+
] }) });
|
|
3618
|
+
}
|
|
3619
|
+
function notify(options) {
|
|
3620
|
+
const { duration, ...toastProps } = options;
|
|
3621
|
+
return toast.custom((id) => /* @__PURE__ */ jsx(NotificationToast, { ...toastProps, onDismiss: () => toast.dismiss(id) }), { duration });
|
|
3622
|
+
}
|
|
2932
3623
|
function AppScreenRoot({ children, className }) {
|
|
2933
3624
|
return /* @__PURE__ */ jsx("div", { className: cn("flex h-full flex-col", className), children });
|
|
2934
3625
|
}
|
|
@@ -3324,7 +4015,7 @@ function PageHeader({
|
|
|
3324
4015
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1 pb-6", className), children: [
|
|
3325
4016
|
breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "mb-2 flex items-center gap-1 text-sm text-muted-foreground", children: breadcrumbs.map((crumb, i) => {
|
|
3326
4017
|
const isLast = i === breadcrumbs.length - 1;
|
|
3327
|
-
return /* @__PURE__ */ jsxs(
|
|
4018
|
+
return /* @__PURE__ */ jsxs(React30.Fragment, { children: [
|
|
3328
4019
|
i > 0 && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5 shrink-0" }),
|
|
3329
4020
|
isLast || !crumb.href ? /* @__PURE__ */ jsx("span", { className: cn(isLast && "text-foreground font-medium"), children: crumb.label }) : renderLink({
|
|
3330
4021
|
href: crumb.href,
|
|
@@ -3360,7 +4051,7 @@ function PageHeader({
|
|
|
3360
4051
|
] });
|
|
3361
4052
|
}
|
|
3362
4053
|
function Suspensed({ children, fallback }) {
|
|
3363
|
-
return /* @__PURE__ */ jsx(
|
|
4054
|
+
return /* @__PURE__ */ jsx(React30.Suspense, { fallback: fallback ?? /* @__PURE__ */ jsx(Spinner, {}), children });
|
|
3364
4055
|
}
|
|
3365
4056
|
function getInitialTheme() {
|
|
3366
4057
|
if (typeof document === "undefined") return "dark";
|
|
@@ -3368,7 +4059,7 @@ function getInitialTheme() {
|
|
|
3368
4059
|
return stored ?? (document.documentElement.classList.contains("dark") ? "dark" : "light");
|
|
3369
4060
|
}
|
|
3370
4061
|
function ThemeSwitchMinimal() {
|
|
3371
|
-
const [theme, setThemeState] =
|
|
4062
|
+
const [theme, setThemeState] = React30.useState(getInitialTheme);
|
|
3372
4063
|
const toggle = () => {
|
|
3373
4064
|
const next = theme === "dark" ? "light" : "dark";
|
|
3374
4065
|
setThemeState(next);
|
|
@@ -3494,9 +4185,9 @@ function getInitialColorName() {
|
|
|
3494
4185
|
return localStorage.getItem(STORAGE_KEY) ?? "emerald";
|
|
3495
4186
|
}
|
|
3496
4187
|
function BrandColorSwitchMinimal() {
|
|
3497
|
-
const [active, setActive] =
|
|
3498
|
-
const [open, setOpen] =
|
|
3499
|
-
|
|
4188
|
+
const [active, setActive] = React30.useState(getInitialColorName);
|
|
4189
|
+
const [open, setOpen] = React30.useState(false);
|
|
4190
|
+
React30.useEffect(() => {
|
|
3500
4191
|
const saved = localStorage.getItem(STORAGE_KEY) ?? "emerald";
|
|
3501
4192
|
const color = BRAND_COLORS.find((c) => c.name === saved);
|
|
3502
4193
|
if (color) applyColor(color);
|
|
@@ -3557,7 +4248,7 @@ function BrandColorSwitchMinimal() {
|
|
|
3557
4248
|
] });
|
|
3558
4249
|
}
|
|
3559
4250
|
function ThemeColorUpdater({ colorTheme }) {
|
|
3560
|
-
|
|
4251
|
+
React30.useEffect(() => {
|
|
3561
4252
|
if (colorTheme) {
|
|
3562
4253
|
document.documentElement.setAttribute("data-theme", colorTheme);
|
|
3563
4254
|
} else {
|
|
@@ -3567,7 +4258,7 @@ function ThemeColorUpdater({ colorTheme }) {
|
|
|
3567
4258
|
return null;
|
|
3568
4259
|
}
|
|
3569
4260
|
function VersionBanner({ version, message, className, onDismiss }) {
|
|
3570
|
-
const [dismissed, setDismissed] =
|
|
4261
|
+
const [dismissed, setDismissed] = React30.useState(false);
|
|
3571
4262
|
if (dismissed) return null;
|
|
3572
4263
|
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-4 bg-primary/10 px-4 py-2 text-sm", className), children: [
|
|
3573
4264
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -3606,23 +4297,6 @@ function DevBanner({ environment = "development", className }) {
|
|
|
3606
4297
|
}
|
|
3607
4298
|
);
|
|
3608
4299
|
}
|
|
3609
|
-
function RegisterHotkeys({ hotkeys }) {
|
|
3610
|
-
React29.useEffect(() => {
|
|
3611
|
-
const handleKeyDown = (event) => {
|
|
3612
|
-
for (const hotkey of hotkeys) {
|
|
3613
|
-
const matches = event.key.toLowerCase() === hotkey.key.toLowerCase() && !!event.ctrlKey === !!hotkey.ctrl && !!event.metaKey === !!hotkey.meta && !!event.shiftKey === !!hotkey.shift && !!event.altKey === !!hotkey.alt;
|
|
3614
|
-
if (matches) {
|
|
3615
|
-
event.preventDefault();
|
|
3616
|
-
hotkey.handler();
|
|
3617
|
-
break;
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
};
|
|
3621
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
3622
|
-
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
3623
|
-
}, [hotkeys]);
|
|
3624
|
-
return null;
|
|
3625
|
-
}
|
|
3626
4300
|
function RootErrorFallback({ error, reset }) {
|
|
3627
4301
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col items-center justify-center gap-6 p-8 text-center", children: [
|
|
3628
4302
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
|
|
@@ -3841,36 +4515,29 @@ function useMounted() {
|
|
|
3841
4515
|
return mounted;
|
|
3842
4516
|
}
|
|
3843
4517
|
function useConfirm() {
|
|
3844
|
-
const [isOpen, setIsOpen] =
|
|
3845
|
-
const [options, setOptions] =
|
|
3846
|
-
const resolveRef =
|
|
3847
|
-
const confirm =
|
|
4518
|
+
const [isOpen, setIsOpen] = React30.useState(false);
|
|
4519
|
+
const [options, setOptions] = React30.useState({});
|
|
4520
|
+
const resolveRef = React30.useRef(null);
|
|
4521
|
+
const confirm = React30.useCallback((opts = {}) => {
|
|
3848
4522
|
setOptions(opts);
|
|
3849
4523
|
setIsOpen(true);
|
|
3850
4524
|
return new Promise((resolve) => {
|
|
3851
4525
|
resolveRef.current = resolve;
|
|
3852
4526
|
});
|
|
3853
4527
|
}, []);
|
|
3854
|
-
const handleConfirm =
|
|
4528
|
+
const handleConfirm = React30.useCallback(() => {
|
|
3855
4529
|
resolveRef.current?.(true);
|
|
3856
4530
|
setIsOpen(false);
|
|
3857
4531
|
}, []);
|
|
3858
|
-
const handleCancel =
|
|
4532
|
+
const handleCancel = React30.useCallback(() => {
|
|
3859
4533
|
resolveRef.current?.(false);
|
|
3860
4534
|
setIsOpen(false);
|
|
3861
4535
|
}, []);
|
|
3862
4536
|
return { confirm, isOpen, options, handleConfirm, handleCancel };
|
|
3863
4537
|
}
|
|
3864
|
-
function useControlledOpen(defaultOpen = false) {
|
|
3865
|
-
const [isOpen, setIsOpen] = React29.useState(defaultOpen);
|
|
3866
|
-
const open = React29.useCallback(() => setIsOpen(true), []);
|
|
3867
|
-
const close = React29.useCallback(() => setIsOpen(false), []);
|
|
3868
|
-
const toggle = React29.useCallback(() => setIsOpen((prev) => !prev), []);
|
|
3869
|
-
return { isOpen, open, close, toggle, setIsOpen };
|
|
3870
|
-
}
|
|
3871
4538
|
function useScrolledPast(threshold) {
|
|
3872
|
-
const [scrolledPast, setScrolledPast] =
|
|
3873
|
-
|
|
4539
|
+
const [scrolledPast, setScrolledPast] = React30.useState(false);
|
|
4540
|
+
React30.useEffect(() => {
|
|
3874
4541
|
const handleScroll = () => {
|
|
3875
4542
|
setScrolledPast(window.scrollY > threshold);
|
|
3876
4543
|
};
|
|
@@ -3881,30 +4548,30 @@ function useScrolledPast(threshold) {
|
|
|
3881
4548
|
return scrolledPast;
|
|
3882
4549
|
}
|
|
3883
4550
|
function useCopyToClipboard() {
|
|
3884
|
-
const [copied, setCopied] =
|
|
3885
|
-
const timeoutRef =
|
|
3886
|
-
const copy =
|
|
4551
|
+
const [copied, setCopied] = React30.useState(false);
|
|
4552
|
+
const timeoutRef = React30.useRef(void 0);
|
|
4553
|
+
const copy = React30.useCallback((text) => {
|
|
3887
4554
|
navigator.clipboard.writeText(text).then(() => {
|
|
3888
4555
|
setCopied(true);
|
|
3889
4556
|
clearTimeout(timeoutRef.current);
|
|
3890
4557
|
timeoutRef.current = setTimeout(() => setCopied(false), 2e3);
|
|
3891
4558
|
});
|
|
3892
4559
|
}, []);
|
|
3893
|
-
|
|
4560
|
+
React30.useEffect(() => () => clearTimeout(timeoutRef.current), []);
|
|
3894
4561
|
return [copied, copy];
|
|
3895
4562
|
}
|
|
3896
4563
|
function useDebounce(value, delay) {
|
|
3897
|
-
const [debounced, setDebounced] =
|
|
3898
|
-
|
|
4564
|
+
const [debounced, setDebounced] = React30.useState(value);
|
|
4565
|
+
React30.useEffect(() => {
|
|
3899
4566
|
const timer = setTimeout(() => setDebounced(value), delay);
|
|
3900
4567
|
return () => clearTimeout(timer);
|
|
3901
4568
|
}, [value, delay]);
|
|
3902
4569
|
return debounced;
|
|
3903
4570
|
}
|
|
3904
4571
|
function useIntersectionObserver(options) {
|
|
3905
|
-
const ref =
|
|
3906
|
-
const [isIntersecting, setIsIntersecting] =
|
|
3907
|
-
|
|
4572
|
+
const ref = React30.useRef(null);
|
|
4573
|
+
const [isIntersecting, setIsIntersecting] = React30.useState(false);
|
|
4574
|
+
React30.useEffect(() => {
|
|
3908
4575
|
const el = ref.current;
|
|
3909
4576
|
if (!el) return;
|
|
3910
4577
|
const observer = new IntersectionObserver(([entry]) => {
|
|
@@ -3916,7 +4583,7 @@ function useIntersectionObserver(options) {
|
|
|
3916
4583
|
return [ref, isIntersecting];
|
|
3917
4584
|
}
|
|
3918
4585
|
function useLocalStorage(key, initialValue) {
|
|
3919
|
-
const [storedValue, setStoredValue] =
|
|
4586
|
+
const [storedValue, setStoredValue] = React30.useState(() => {
|
|
3920
4587
|
if (typeof window === "undefined") return initialValue;
|
|
3921
4588
|
try {
|
|
3922
4589
|
const item = window.localStorage.getItem(key);
|
|
@@ -3925,7 +4592,7 @@ function useLocalStorage(key, initialValue) {
|
|
|
3925
4592
|
return initialValue;
|
|
3926
4593
|
}
|
|
3927
4594
|
});
|
|
3928
|
-
const setValue =
|
|
4595
|
+
const setValue = React30.useCallback(
|
|
3929
4596
|
(value) => {
|
|
3930
4597
|
try {
|
|
3931
4598
|
setStoredValue(value);
|
|
@@ -3940,11 +4607,11 @@ function useLocalStorage(key, initialValue) {
|
|
|
3940
4607
|
return [storedValue, setValue];
|
|
3941
4608
|
}
|
|
3942
4609
|
function useMediaQuery(query) {
|
|
3943
|
-
const [matches, setMatches] =
|
|
4610
|
+
const [matches, setMatches] = React30.useState(() => {
|
|
3944
4611
|
if (typeof window === "undefined") return false;
|
|
3945
4612
|
return window.matchMedia(query).matches;
|
|
3946
4613
|
});
|
|
3947
|
-
|
|
4614
|
+
React30.useEffect(() => {
|
|
3948
4615
|
if (typeof window === "undefined") return;
|
|
3949
4616
|
const mql = window.matchMedia(query);
|
|
3950
4617
|
setMatches(mql.matches);
|
|
@@ -3955,18 +4622,18 @@ function useMediaQuery(query) {
|
|
|
3955
4622
|
return matches;
|
|
3956
4623
|
}
|
|
3957
4624
|
function usePagination(total, pageSize) {
|
|
3958
|
-
const [page, setPage] =
|
|
4625
|
+
const [page, setPage] = React30.useState(1);
|
|
3959
4626
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
3960
4627
|
const offset = (page - 1) * pageSize;
|
|
3961
|
-
const goTo =
|
|
4628
|
+
const goTo = React30.useCallback(
|
|
3962
4629
|
(p) => setPage(Math.min(Math.max(1, p), totalPages)),
|
|
3963
4630
|
[totalPages]
|
|
3964
4631
|
);
|
|
3965
|
-
const next =
|
|
3966
|
-
const prev =
|
|
4632
|
+
const next = React30.useCallback(() => goTo(page + 1), [page, goTo]);
|
|
4633
|
+
const prev = React30.useCallback(() => goTo(page - 1), [page, goTo]);
|
|
3967
4634
|
return { page, pageSize, totalPages, offset, hasPrev: page > 1, hasNext: page < totalPages, goTo, next, prev };
|
|
3968
4635
|
}
|
|
3969
4636
|
|
|
3970
|
-
export { AnimatedPill, AppContent, AppGrid, AppGridCell, AppScreen, AppShell, AuthSection, Badge, BrandColorSwitchMinimal, Button, Card, CarouselSection, ChangelogCard, ChangelogList, Checkbox, ComparisonSection, ConfirmDialog, ContactSection, ContextMenu, CountUp, CountdownTimer, CtaCardSection, CtaSection, DevBanner, Dock, EmptyState, ErrorPage, FadeIn, FaqSection, FeatureCard, FeatureMarqueeSection, FeaturesSection, FlowBracket, FlowConnector, Footer, FullPageSpinner, GlassCard, Grid, GridItem, GroupedFeaturesSection, HeroSection, ImageCard, Input, LogoStrip, ModeSwitchMinimal, Navbar, NewsletterSection, OlwibaUIProvider, Overlay, PageHeader, PageTransition, PhoneFrame, PostCard, PostList, PricingCard, PricingSection, PublicPageFrame, QualificationSection, RegisterHotkeys, RootErrorFallback, Section, SectionTitle, Spotlight, Stack, StaggerChildren, StatCard, StatsSection, StepsSection, Suspensed, Switch, TeamSection, TechStackSection, TestimonialCard, TestimonialsSection, Textarea, ThemeColorUpdater, ThemeSwitchMinimal, Underlay, UpgradePrompt, VersionBanner, marketingSectionSpacing, useConfirm, useControlledOpen, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMediaQuery, useMounted, useOlwibaUI, usePagination, useScrolledPast, useUIMode };
|
|
4637
|
+
export { AnimatedPill, AppContent, AppGrid, AppGridCell, AppScreen, AppShell, AuthSection, Badge, BillingPanel, BrandColorSwitchMinimal, Button, Card, CarouselSection, ChangelogCard, ChangelogList, Checkbox, CommandMenu, ComparisonSection, ConfirmDialog, ContactSection, ContextMenu, CountUp, CountdownTimer, CtaCardSection, CtaSection, DataTable, DevBanner, Dock, EmptyState, ErrorPage, FadeIn, FaqSection, FeatureCard, FeatureMarqueeSection, FeaturesSection, FileUpload, FlowBracket, FlowConnector, Footer, FullPageSpinner, GlassCard, Grid, GridItem, GroupedFeaturesSection, HeroSection, ImageCard, Input, LogoStrip, ModeSwitchMinimal, Navbar, NewsletterSection, NotificationToast, OlwibaUIProvider, OnboardingWizard, Overlay, PageHeader, PageTransition, PhoneFrame, PostCard, PostList, PricingCard, PricingSection, PublicPageFrame, QualificationSection, RegisterHotkeys, RootErrorFallback, Section, SectionTitle, SettingsSection, Spotlight, Stack, StaggerChildren, StatCard, StatsSection, StepsSection, Suspensed, Switch, TeamMembersPanel, TeamSection, TechStackSection, TestimonialCard, TestimonialsSection, Textarea, ThemeColorUpdater, ThemeSwitchMinimal, Underlay, UpgradePrompt, VersionBanner, marketingSectionSpacing, notify, useConfirm, useControlledOpen, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMediaQuery, useMounted, useOlwibaUI, usePagination, useScrolledPast, useUIMode };
|
|
3971
4638
|
//# sourceMappingURL=index.js.map
|
|
3972
4639
|
//# sourceMappingURL=index.js.map
|