@olwiba/ui 0.2.6 → 0.2.8

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.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { cn } from './chunk-AMWLSHD6.js';
2
2
  export { MdxContent, cn } from './chunk-AMWLSHD6.js';
3
- import * as React34 from 'react';
3
+ import * as React35 from 'react';
4
4
  import { useRef, useEffect, useState } from 'react';
5
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, Popover, PopoverTrigger, PopoverContent, Spinner, 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
6
  export { CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Enchanted } from '@olwiba/cn';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { Search, ArrowUp, ArrowDown, ArrowUpDown, ChevronLeft, ChevronRight, MoreHorizontal, UserPlus, Download, CreditCard, Check, Loader2, ArrowLeft, ArrowUpRight, X, Sparkles, RefreshCw, ArrowRight, Minus, Quote, Star, Twitter, Github, Linkedin, Mail, MessageSquare, ChevronDown, Send, Menu, UploadCloud, File, CheckCircle2, AlertCircle, Bell, Inbox, Activity, TrendingUp, TrendingDown, Sun, Moon, Palette, AlertTriangle, Rocket, Info, Layers, LayoutGrid, ShieldCheck, Building2, MoreVerticalIcon, CreditCardIcon, BellIcon, LogOutIcon } from 'lucide-react';
8
+ import { Search, ArrowUp, ArrowDown, ArrowUpDown, ChevronLeft, ChevronRight, MoreHorizontal, UserPlus, Download, CreditCard, Check, Loader2, ArrowLeft, ArrowUpRight, X, Sparkles, RefreshCw, ArrowRight, Minus, Quote, Star, Twitter, Github, Linkedin, Mail, MessageSquare, ChevronDown, Send, Menu, UploadCloud, File, CheckCircle2, AlertCircle, Bell, Inbox, Activity, TrendingUp, TrendingDown, LayoutGrid, List, Sun, Moon, Palette, AlertTriangle, Rocket, Info, Layers, ShieldCheck, Building2, MoreVerticalIcon, CreditCardIcon, BellIcon, LogOutIcon } from 'lucide-react';
9
9
  import { useReactTable, getPaginationRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
10
10
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
11
11
  import { sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, horizontalListSortingStrategy, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
@@ -13,7 +13,7 @@ import { CSS } from '@dnd-kit/utilities';
13
13
  import { PieChart, Pie, Cell, BarChart, XAxis, YAxis, Bar, AreaChart, Area, LineChart, Line, ResponsiveContainer, Tooltip as Tooltip$1, CartesianGrid } from 'recharts';
14
14
  import { toast } from 'sonner';
15
15
 
16
- var OlwibaUIContext = React34.createContext({
16
+ var OlwibaUIContext = React35.createContext({
17
17
  isMobile: false,
18
18
  mode: "default",
19
19
  setMode: () => {
@@ -22,17 +22,17 @@ var OlwibaUIContext = React34.createContext({
22
22
  function OlwibaUIProvider({ children, isMobile: isMobileProp, mode: initialMode = "default" }) {
23
23
  const detectedMobile = useIsMobile();
24
24
  const isMobile = isMobileProp ?? detectedMobile;
25
- const [mode, setMode] = React34.useState(initialMode);
25
+ const [mode, setMode] = React35.useState(initialMode);
26
26
  const variant = mode !== "default" ? mode : void 0;
27
27
  return /* @__PURE__ */ jsx(OlwibaUIContext.Provider, { value: { isMobile, mode, setMode }, children: /* @__PURE__ */ jsx(UIVariantProvider, { mode: variant, children }) });
28
28
  }
29
29
  function useOlwibaUI() {
30
- return React34.useContext(OlwibaUIContext);
30
+ return React35.useContext(OlwibaUIContext);
31
31
  }
32
32
  function useUIMode() {
33
- return React34.useContext(OlwibaUIContext).mode;
33
+ return React35.useContext(OlwibaUIContext).mode;
34
34
  }
35
- var Button = React34.forwardRef(
35
+ var Button = React35.forwardRef(
36
36
  ({ mode, ...props }, ref) => {
37
37
  const ctxMode = useUIMode();
38
38
  const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
@@ -40,7 +40,7 @@ var Button = React34.forwardRef(
40
40
  }
41
41
  );
42
42
  Button.displayName = "Button";
43
- var Card = React34.forwardRef(
43
+ var Card = React35.forwardRef(
44
44
  ({ mode, ...props }, ref) => {
45
45
  const ctxMode = useUIMode();
46
46
  const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
@@ -51,7 +51,7 @@ Card.displayName = "Card";
51
51
  function Badge(props) {
52
52
  return /* @__PURE__ */ jsx(Badge$1, { ...props });
53
53
  }
54
- var Input = React34.forwardRef(
54
+ var Input = React35.forwardRef(
55
55
  ({ mode, ...props }, ref) => {
56
56
  const ctxMode = useUIMode();
57
57
  const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
@@ -59,7 +59,7 @@ var Input = React34.forwardRef(
59
59
  }
60
60
  );
61
61
  Input.displayName = "Input";
62
- var Textarea = React34.forwardRef(
62
+ var Textarea = React35.forwardRef(
63
63
  ({ mode, ...props }, ref) => {
64
64
  const ctxMode = useUIMode();
65
65
  const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
@@ -67,13 +67,13 @@ var Textarea = React34.forwardRef(
67
67
  }
68
68
  );
69
69
  Textarea.displayName = "Textarea";
70
- var Checkbox = React34.forwardRef(({ mode, ...props }, ref) => {
70
+ var Checkbox = React35.forwardRef(({ mode, ...props }, ref) => {
71
71
  const ctxMode = useUIMode();
72
72
  const resolvedMode = mode ?? (ctxMode !== "default" ? ctxMode : void 0);
73
73
  return /* @__PURE__ */ jsx(Checkbox$1, { mode: resolvedMode, ...props, ref });
74
74
  });
75
75
  Checkbox.displayName = "Checkbox";
76
- var Switch = React34.forwardRef(({ mode: _mode, ...props }, ref) => {
76
+ var Switch = React35.forwardRef(({ mode: _mode, ...props }, ref) => {
77
77
  return /* @__PURE__ */ jsx(Switch$1, { ...props, ref });
78
78
  });
79
79
  Switch.displayName = "Switch";
@@ -212,19 +212,22 @@ function AppContent({
212
212
  }
213
213
  var columnsMap = {
214
214
  1: "grid-cols-1",
215
- 2: "grid-cols-1 md:grid-cols-2",
216
- 3: "grid-cols-1 md:grid-cols-2 xl:grid-cols-3",
217
- 4: "grid-cols-1 md:grid-cols-2 xl:grid-cols-4"
215
+ 2: "grid-cols-1 sm:grid-cols-2",
216
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
217
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
218
+ 5: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5",
219
+ 6: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6"
218
220
  };
219
221
  var gapMap3 = {
222
+ none: "gap-0",
220
223
  sm: "gap-3",
221
224
  md: "gap-4",
222
225
  lg: "gap-6"
223
226
  };
224
227
  var spanMap2 = {
225
- 1: "md:col-span-1",
226
- 2: "md:col-span-2",
227
- 3: "xl:col-span-3",
228
+ 1: "sm:col-span-1",
229
+ 2: "sm:col-span-2",
230
+ 3: "lg:col-span-3",
228
231
  4: "xl:col-span-4",
229
232
  full: "col-span-full"
230
233
  };
@@ -531,7 +534,7 @@ function DefaultForm({
531
534
  const isForgotPassword = mode === "forgot-password";
532
535
  const isResetPassword = mode === "reset-password";
533
536
  const isVerify = mode === "verify";
534
- const [code, setCode] = React34.useState("");
537
+ const [code, setCode] = React35.useState("");
535
538
  const hasPrefill = !!(defaultEmail || defaultPassword);
536
539
  const prefillStyle = (active) => active ? { animation: "auth-prefill 1.6s ease-out 0.35s 1 both" } : void 0;
537
540
  const copy = authCopy[mode];
@@ -691,10 +694,10 @@ function DataTable({
691
694
  emptyMessage = "No results.",
692
695
  className
693
696
  }) {
694
- const [sorting, setSorting] = React34.useState([]);
695
- const [rowSelection, setRowSelection] = React34.useState({});
696
- const [globalFilter, setGlobalFilter] = React34.useState("");
697
- const allColumns = React34.useMemo(
697
+ const [sorting, setSorting] = React35.useState([]);
698
+ const [rowSelection, setRowSelection] = React35.useState({});
699
+ const [globalFilter, setGlobalFilter] = React35.useState("");
700
+ const allColumns = React35.useMemo(
698
701
  () => selectable ? [selectColumn, ...columns] : columns,
699
702
  [selectable, columns]
700
703
  );
@@ -716,7 +719,7 @@ function DataTable({
716
719
  initialState: pageSize > 0 ? { pagination: { pageSize } } : void 0,
717
720
  enableRowSelection: selectable
718
721
  });
719
- React34.useEffect(() => {
722
+ React35.useEffect(() => {
720
723
  if (!onSelectionChange) return;
721
724
  onSelectionChange(table.getSelectedRowModel().rows.map((r) => r.original));
722
725
  }, [rowSelection]);
@@ -796,8 +799,8 @@ function DataTable({
796
799
  ] });
797
800
  }
798
801
  function InviteDialog({ roles, onInvite, onClose }) {
799
- const [email, setEmail] = React34.useState("");
800
- const [role, setRole] = React34.useState(roles[roles.length - 1] ?? roles[0]);
802
+ const [email, setEmail] = React35.useState("");
803
+ const [role, setRole] = React35.useState(roles[roles.length - 1] ?? roles[0]);
801
804
  return /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(
802
805
  "form",
803
806
  {
@@ -841,8 +844,8 @@ function TeamMembersPanel({
841
844
  title = "Team members",
842
845
  description = "Manage who has access to this workspace."
843
846
  }) {
844
- const [inviteOpen, setInviteOpen] = React34.useState(false);
845
- const columns = React34.useMemo(() => [
847
+ const [inviteOpen, setInviteOpen] = React35.useState(false);
848
+ const columns = React35.useMemo(() => [
846
849
  {
847
850
  accessorKey: "name",
848
851
  header: "Name",
@@ -911,7 +914,7 @@ function BillingPanel({
911
914
  onUpdatePaymentMethod,
912
915
  className
913
916
  }) {
914
- const invoiceColumns = React34.useMemo(() => [
917
+ const invoiceColumns = React35.useMemo(() => [
915
918
  { accessorKey: "date", header: "Date", enableSorting: false },
916
919
  { accessorKey: "amount", header: "Amount", enableSorting: false },
917
920
  {
@@ -990,9 +993,9 @@ function OnboardingWizard({
990
993
  completeLabel = "Finish",
991
994
  className
992
995
  }) {
993
- const [internalStep, setInternalStep] = React34.useState(0);
994
- const [error, setError] = React34.useState(null);
995
- const [pending, setPending] = React34.useState(false);
996
+ const [internalStep, setInternalStep] = React35.useState(0);
997
+ const [error, setError] = React35.useState(null);
998
+ const [pending, setPending] = React35.useState(false);
996
999
  const index = stepProp ?? internalStep;
997
1000
  const current = steps[index];
998
1001
  const isLast = index === steps.length - 1;
@@ -1322,10 +1325,10 @@ function UpdateBanner({
1322
1325
  buttonLabel = "Refresh now",
1323
1326
  className
1324
1327
  }) {
1325
- const [show, setShow] = React34.useState(forceShow);
1326
- const [entered, setEntered] = React34.useState(false);
1327
- const [refreshing, setRefreshing] = React34.useState(false);
1328
- React34.useEffect(() => {
1328
+ const [show, setShow] = React35.useState(forceShow);
1329
+ const [entered, setEntered] = React35.useState(false);
1330
+ const [refreshing, setRefreshing] = React35.useState(false);
1331
+ React35.useEffect(() => {
1329
1332
  if (forceShow) {
1330
1333
  setShow(true);
1331
1334
  return;
@@ -1348,7 +1351,7 @@ function UpdateBanner({
1348
1351
  clearInterval(id);
1349
1352
  };
1350
1353
  }, [currentVersion, fetchVersion, forceShow, intervalMs]);
1351
- React34.useEffect(() => {
1354
+ React35.useEffect(() => {
1352
1355
  if (!show) {
1353
1356
  setEntered(false);
1354
1357
  return;
@@ -1421,9 +1424,9 @@ function FadeIn({
1421
1424
  style,
1422
1425
  ...props
1423
1426
  }) {
1424
- const ref = React34.useRef(null);
1425
- const [visible, setVisible] = React34.useState(false);
1426
- React34.useEffect(() => {
1427
+ const ref = React35.useRef(null);
1428
+ const [visible, setVisible] = React35.useState(false);
1429
+ React35.useEffect(() => {
1427
1430
  const el = ref.current;
1428
1431
  if (!el) return;
1429
1432
  const observer = new IntersectionObserver(
@@ -1618,16 +1621,16 @@ function Carousel({
1618
1621
  className
1619
1622
  }) {
1620
1623
  const mode = useUIVariant();
1621
- const trackRef = React34.useRef(null);
1622
- const [canPrev, setCanPrev] = React34.useState(false);
1623
- const [canNext, setCanNext] = React34.useState(true);
1624
- const updateScrollState = React34.useCallback(() => {
1624
+ const trackRef = React35.useRef(null);
1625
+ const [canPrev, setCanPrev] = React35.useState(false);
1626
+ const [canNext, setCanNext] = React35.useState(true);
1627
+ const updateScrollState = React35.useCallback(() => {
1625
1628
  const track = trackRef.current;
1626
1629
  if (!track) return;
1627
1630
  setCanPrev(track.scrollLeft > 8);
1628
1631
  setCanNext(track.scrollLeft < track.scrollWidth - track.clientWidth - 8);
1629
1632
  }, []);
1630
- React34.useEffect(() => {
1633
+ React35.useEffect(() => {
1631
1634
  const track = trackRef.current;
1632
1635
  if (!track) return;
1633
1636
  updateScrollState();
@@ -1684,7 +1687,7 @@ function Carousel({
1684
1687
  role: "region",
1685
1688
  "aria-label": ariaLabel,
1686
1689
  className: "flex snap-x snap-mandatory gap-6 overflow-x-auto pb-4 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
1687
- children: React34.Children.map(
1690
+ children: React35.Children.map(
1688
1691
  children,
1689
1692
  (child) => child == null ? null : /* @__PURE__ */ jsx("div", { "data-carousel-item": true, className: cn$1("shrink-0 snap-start", itemClassName), children: child })
1690
1693
  )
@@ -1709,9 +1712,9 @@ function StaggerChildren({
1709
1712
  className,
1710
1713
  ...props
1711
1714
  }) {
1712
- const ref = React34.useRef(null);
1713
- const [visible, setVisible] = React34.useState(false);
1714
- React34.useEffect(() => {
1715
+ const ref = React35.useRef(null);
1716
+ const [visible, setVisible] = React35.useState(false);
1717
+ React35.useEffect(() => {
1715
1718
  const el = ref.current;
1716
1719
  if (!el) return;
1717
1720
  const observer = new IntersectionObserver(
@@ -1729,7 +1732,7 @@ function StaggerChildren({
1729
1732
  return () => observer.disconnect();
1730
1733
  }, [once]);
1731
1734
  const [tx, ty] = translateMap2[direction];
1732
- return /* @__PURE__ */ jsx("div", { ref, className: cn$1(className), ...props, children: React34.Children.map(children, (child, i) => /* @__PURE__ */ jsx(
1735
+ return /* @__PURE__ */ jsx("div", { ref, className: cn$1(className), ...props, children: React35.Children.map(children, (child, i) => /* @__PURE__ */ jsx(
1733
1736
  "div",
1734
1737
  {
1735
1738
  className: "h-full",
@@ -1871,7 +1874,7 @@ function StepsSection({
1871
1874
  /* @__PURE__ */ jsx("div", { className: "mt-12", children: resolvedGroups.map((group, groupIndex) => {
1872
1875
  const startNumber = nextNumber;
1873
1876
  nextNumber += group.steps.length;
1874
- return /* @__PURE__ */ jsxs(React34.Fragment, { children: [
1877
+ return /* @__PURE__ */ jsxs(React35.Fragment, { children: [
1875
1878
  /* @__PURE__ */ jsx("div", { className: cn$1(groupIndex > 0 && "mt-10"), children: /* @__PURE__ */ jsx(TimelineGroup, { steps: group.steps, startNumber, fadeIn: groupIndex > 0 }) }),
1876
1879
  group.after && /* @__PURE__ */ jsx("div", { className: "mt-16", children: group.after })
1877
1880
  ] }, groupIndex);
@@ -2041,9 +2044,9 @@ function FeatureMarqueeSection({
2041
2044
  ] });
2042
2045
  }
2043
2046
  function useIntersectionObserver(options) {
2044
- const ref = React34.useRef(null);
2045
- const [isIntersecting, setIsIntersecting] = React34.useState(false);
2046
- React34.useEffect(() => {
2047
+ const ref = React35.useRef(null);
2048
+ const [isIntersecting, setIsIntersecting] = React35.useState(false);
2049
+ React35.useEffect(() => {
2047
2050
  const el = ref.current;
2048
2051
  if (!el) return;
2049
2052
  const observer = new IntersectionObserver(([entry]) => {
@@ -2065,8 +2068,8 @@ function ShowcaseCta({
2065
2068
  sectionClasses
2066
2069
  }) {
2067
2070
  const [ref, intersecting] = useIntersectionObserver({ threshold: 0.15 });
2068
- const [visible, setVisible] = React34.useState(false);
2069
- React34.useEffect(() => {
2071
+ const [visible, setVisible] = React35.useState(false);
2072
+ React35.useEffect(() => {
2070
2073
  if (intersecting) setVisible(true);
2071
2074
  }, [intersecting]);
2072
2075
  return /* @__PURE__ */ jsxs(
@@ -2243,8 +2246,8 @@ function CountdownTimer({
2243
2246
  className,
2244
2247
  compact = false
2245
2248
  }) {
2246
- const [timeLeft, setTimeLeft] = React34.useState(() => getTimeLeft(deadline));
2247
- React34.useEffect(() => {
2249
+ const [timeLeft, setTimeLeft] = React35.useState(() => getTimeLeft(deadline));
2250
+ React35.useEffect(() => {
2248
2251
  setTimeLeft(getTimeLeft(deadline));
2249
2252
  const id = setInterval(() => setTimeLeft(getTimeLeft(deadline)), 1e3);
2250
2253
  return () => clearInterval(id);
@@ -2270,7 +2273,7 @@ function CountdownTimer({
2270
2273
  ];
2271
2274
  return /* @__PURE__ */ jsxs("div", { className: cn$1("flex items-center justify-center gap-2 text-sm", className), children: [
2272
2275
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
2273
- /* @__PURE__ */ jsx("span", { className: "flex items-center gap-1 font-mono font-semibold tabular-nums", children: units.map(({ value, unit }, i) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
2276
+ /* @__PURE__ */ jsx("span", { className: "flex items-center gap-1 font-mono font-semibold tabular-nums", children: units.map(({ value, unit }, i) => /* @__PURE__ */ jsxs(React35.Fragment, { children: [
2274
2277
  /* @__PURE__ */ jsxs("span", { children: [
2275
2278
  String(value).padStart(2, "0"),
2276
2279
  /* @__PURE__ */ jsx("span", { className: "font-normal text-muted-foreground", children: unit })
@@ -2279,6 +2282,32 @@ function CountdownTimer({
2279
2282
  ] }, unit)) })
2280
2283
  ] });
2281
2284
  }
2285
+ function computeSaveBadges(plans, cadences, defaultKey) {
2286
+ const annualised = (key) => {
2287
+ const cadence = cadences.find((c) => c.key === key);
2288
+ if (!cadence?.periodsPerYear) return null;
2289
+ const totals = plans.filter((plan) => plan.priceDisplay === void 0 && plan.prices?.[key] !== void 0).map((plan) => plan.prices[key] * cadence.periodsPerYear);
2290
+ return totals.length > 0 ? totals.reduce((sum, n) => sum + n, 0) : null;
2291
+ };
2292
+ const baseline = annualised(defaultKey);
2293
+ if (!baseline) return {};
2294
+ const badges = {};
2295
+ for (const cadence of cadences) {
2296
+ if (cadence.key === defaultKey) continue;
2297
+ const total = annualised(cadence.key);
2298
+ if (!total || total >= baseline) continue;
2299
+ const percent = Math.round((baseline - total) / baseline * 100);
2300
+ if (percent > 0) badges[cadence.key] = `Save ${percent}%`;
2301
+ }
2302
+ return badges;
2303
+ }
2304
+ function gridClassesFor(count) {
2305
+ if (count <= 1) return "mx-auto max-w-sm";
2306
+ if (count === 2) return "mx-auto max-w-3xl sm:grid-cols-2";
2307
+ if (count === 3) return "mx-auto max-w-5xl lg:grid-cols-3";
2308
+ if (count === 4) return "mx-auto max-w-6xl sm:grid-cols-2 lg:grid-cols-4";
2309
+ return "mx-auto max-w-5xl sm:grid-cols-2 lg:grid-cols-3";
2310
+ }
2282
2311
  var defaultRenderLink6 = ({ href, children, className }) => /* @__PURE__ */ jsx("a", { href, className, children });
2283
2312
  function PricingSection({
2284
2313
  title = "Simple, transparent pricing",
@@ -2293,10 +2322,21 @@ function PricingSection({
2293
2322
  foundingDeadline,
2294
2323
  currency = "$",
2295
2324
  highlightedBadgeLabel = "Founding member",
2296
- renderPlanFooter
2325
+ renderPlanFooter,
2326
+ cadences,
2327
+ defaultCadence
2297
2328
  }) {
2298
- const [annual, setAnnual] = React34.useState(false);
2329
+ const [annual, setAnnual] = React35.useState(false);
2299
2330
  const isOneTime = mode === "one-time";
2331
+ const useCadences = !!cadences?.length;
2332
+ const initialCadence = (defaultCadence && cadences?.some((c) => c.key === defaultCadence) ? defaultCadence : null) ?? cadences?.[0]?.key ?? "";
2333
+ const [activeCadence, setActiveCadence] = React35.useState(initialCadence);
2334
+ const cadence = cadences?.find((c) => c.key === activeCadence);
2335
+ const saveBadges = React35.useMemo(
2336
+ () => useCadences ? computeSaveBadges(plans, cadences, initialCadence) : {},
2337
+ [useCadences, plans, cadences, initialCadence]
2338
+ );
2339
+ const showToggle = useCadences ? cadences.length > 1 : !isOneTime;
2300
2340
  const uiMode = useUIVariant();
2301
2341
  const sectionClasses = cn$1(
2302
2342
  "overflow-hidden bg-card",
@@ -2309,7 +2349,23 @@ function PricingSection({
2309
2349
  badge && (typeof badge === "string" ? /* @__PURE__ */ jsx(Badge$1, { variant: "secondary", className: "mb-4", children: badge }) : /* @__PURE__ */ jsx("div", { className: "mb-4", children: badge })),
2310
2350
  /* @__PURE__ */ jsx("h2", { className: "text-balance text-3xl font-semibold tracking-tight sm:text-4xl", children: title }),
2311
2351
  description && /* @__PURE__ */ jsx("p", { className: "mx-auto mt-4 max-w-xl text-pretty text-muted-foreground", children: description }),
2312
- !isOneTime && /* @__PURE__ */ jsxs("div", { className: "mt-6 inline-flex items-center gap-3 rounded-full border bg-muted p-1", children: [
2352
+ showToggle && /* @__PURE__ */ jsx("div", { className: "mt-6 inline-flex items-center gap-3 rounded-full border bg-muted p-1", children: useCadences ? cadences.map((entry) => /* @__PURE__ */ jsxs(
2353
+ Button$1,
2354
+ {
2355
+ variant: "ghost",
2356
+ size: "sm",
2357
+ onClick: () => setActiveCadence(entry.key),
2358
+ className: cn$1(
2359
+ "flex items-center gap-2 rounded-full px-4 py-1.5 text-sm font-medium transition-colors",
2360
+ entry.key === activeCadence ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
2361
+ ),
2362
+ children: [
2363
+ entry.label,
2364
+ saveBadges[entry.key] && /* @__PURE__ */ jsx(Badge$1, { variant: "secondary", className: "text-xs", children: saveBadges[entry.key] })
2365
+ ]
2366
+ },
2367
+ entry.key
2368
+ )) : /* @__PURE__ */ jsxs(Fragment, { children: [
2313
2369
  /* @__PURE__ */ jsx(
2314
2370
  Button$1,
2315
2371
  {
@@ -2339,12 +2395,12 @@ function PricingSection({
2339
2395
  ]
2340
2396
  }
2341
2397
  )
2342
- ] })
2398
+ ] }) })
2343
2399
  ] }),
2344
- /* @__PURE__ */ jsx(StaggerChildren, { className: "mt-10 grid gap-4 lg:grid-cols-3", children: plans.map((plan) => {
2345
- const rawPrice = isOneTime ? plan.monthly : annual ? plan.annual : plan.monthly;
2400
+ /* @__PURE__ */ jsx(StaggerChildren, { className: cn$1("mt-10 grid gap-4", gridClassesFor(plans.length)), children: plans.map((plan) => {
2401
+ const rawPrice = useCadences ? plan.prices?.[activeCadence] ?? plan.monthly : isOneTime ? plan.monthly : annual ? plan.annual : plan.monthly;
2346
2402
  const price = plan.priceDisplay ?? `${currency}${rawPrice}`;
2347
- const period = plan.periodDisplay ?? (isOneTime ? "one-time" : rawPrice > 0 ? "/mo" : "");
2403
+ const period = useCadences ? cadence?.suffix ?? plan.periodDisplay ?? "" : plan.periodDisplay ?? (isOneTime ? "one-time" : rawPrice > 0 ? "/mo" : "");
2348
2404
  const badge2 = plan.highlighted && foundingDeadline ? /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full border bg-secondary px-2.5 py-0.5 text-xs font-semibold text-secondary-foreground", children: /* @__PURE__ */ jsx(CountdownTimer, { deadline: foundingDeadline, compact: true }) }) : plan.highlighted && highlightedBadgeLabel ? highlightedBadgeLabel : void 0;
2349
2405
  return /* @__PURE__ */ jsx(
2350
2406
  PricingCard,
@@ -2564,10 +2620,10 @@ function CountUp({
2564
2620
  className,
2565
2621
  ...props
2566
2622
  }) {
2567
- const ref = React34.useRef(null);
2568
- const [value, setValue] = React34.useState(from);
2569
- const hasStarted = React34.useRef(false);
2570
- React34.useEffect(() => {
2623
+ const ref = React35.useRef(null);
2624
+ const [value, setValue] = React35.useState(from);
2625
+ const hasStarted = React35.useRef(false);
2626
+ React35.useEffect(() => {
2571
2627
  const el = ref.current;
2572
2628
  if (!el) return;
2573
2629
  const observer = new IntersectionObserver(
@@ -2660,8 +2716,8 @@ function NewsletterSection({
2660
2716
  successDescription = defaults.successDescription,
2661
2717
  onSubscribe
2662
2718
  } = {}) {
2663
- const [email, setEmail] = React34.useState("");
2664
- const [submitted, setSubmitted] = React34.useState(false);
2719
+ const [email, setEmail] = React35.useState("");
2720
+ const [submitted, setSubmitted] = React35.useState(false);
2665
2721
  const mode = useUIVariant();
2666
2722
  const sectionClasses = cn$1(
2667
2723
  "overflow-hidden bg-card",
@@ -2752,7 +2808,7 @@ function ContactSection({
2752
2808
  sendAnotherLabel = defaults2.sendAnotherLabel,
2753
2809
  onSubmit
2754
2810
  } = {}) {
2755
- const [submitted, setSubmitted] = React34.useState(false);
2811
+ const [submitted, setSubmitted] = React35.useState(false);
2756
2812
  const mode = useUIVariant();
2757
2813
  const sectionClasses = cn$1(
2758
2814
  "overflow-hidden bg-card",
@@ -3050,9 +3106,10 @@ function Navbar({
3050
3106
  controls,
3051
3107
  renderLink = defaultRenderLink7
3052
3108
  }) {
3053
- const [open, setOpen] = React34.useState(false);
3109
+ const [open, setOpen] = React35.useState(false);
3054
3110
  const brandHref = brand.href ?? "/";
3055
3111
  const hasRightContent = controls?.length || cta?.primary || cta?.secondary;
3112
+ const closeMobileMenu = React35.useCallback(() => setOpen(false), []);
3056
3113
  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: [
3057
3114
  renderLink({
3058
3115
  href: brandHref,
@@ -3067,7 +3124,7 @@ function Navbar({
3067
3124
  children: link.label
3068
3125
  }) }, link.label)) }),
3069
3126
  hasRightContent && /* @__PURE__ */ jsxs("div", { className: "hidden items-center justify-end gap-2 md:flex", children: [
3070
- controls?.map((control, i) => /* @__PURE__ */ jsx(React34.Fragment, { children: control }, i)),
3127
+ controls?.map((control, i) => /* @__PURE__ */ jsx(React35.Fragment, { children: control }, i)),
3071
3128
  (cta?.secondary || cta?.primary) && controls?.length ? /* @__PURE__ */ jsx("div", { className: "mx-1 h-4 w-px bg-border" }) : null,
3072
3129
  cta?.secondary && renderLink({
3073
3130
  href: cta.secondary.href,
@@ -3084,29 +3141,29 @@ function Navbar({
3084
3141
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Open menu" })
3085
3142
  ] }) }),
3086
3143
  /* @__PURE__ */ jsxs(SheetContent, { side: "left", className: "w-72", children: [
3087
- /* @__PURE__ */ jsx("div", { className: "flex items-center pb-4", children: /* @__PURE__ */ jsx("span", { onClick: () => setOpen(false), className: "cursor-pointer", children: renderLink({
3144
+ /* @__PURE__ */ jsx("div", { className: "flex items-center pb-4", children: /* @__PURE__ */ jsx("span", { onClickCapture: closeMobileMenu, className: "cursor-pointer", children: renderLink({
3088
3145
  href: brandHref,
3089
3146
  children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 font-semibold", children: [
3090
3147
  brand.logo,
3091
3148
  /* @__PURE__ */ jsx("span", { children: brand.name })
3092
3149
  ] })
3093
3150
  }) }) }),
3094
- controls?.length ? /* @__PURE__ */ jsx("div", { className: "mb-4 flex w-full items-center justify-between gap-1", children: controls.map((control, i) => /* @__PURE__ */ jsx(React34.Fragment, { children: control }, i)) }) : null,
3151
+ controls?.length ? /* @__PURE__ */ jsx("div", { className: "mb-4 flex w-full items-center justify-between gap-1", children: controls.map((control, i) => /* @__PURE__ */ jsx(React35.Fragment, { children: control }, i)) }) : null,
3095
3152
  /* @__PURE__ */ jsx(Separator, {}),
3096
- /* @__PURE__ */ jsx("nav", { className: "mt-4 flex flex-col gap-1", children: navLinks.map((link) => /* @__PURE__ */ jsx("span", { onClick: () => setOpen(false), children: renderLink({
3153
+ /* @__PURE__ */ jsx("nav", { className: "mt-4 flex flex-col gap-1", children: navLinks.map((link) => /* @__PURE__ */ jsx("span", { onClickCapture: closeMobileMenu, children: renderLink({
3097
3154
  href: link.href,
3098
3155
  className: "block rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
3099
3156
  children: link.label
3100
3157
  }) }, link.label)) }),
3101
3158
  /* @__PURE__ */ jsxs("div", { className: "mt-6 flex flex-col gap-2", children: [
3102
- cta?.secondary && renderLink({
3159
+ cta?.secondary && /* @__PURE__ */ jsx("span", { onClickCapture: closeMobileMenu, children: renderLink({
3103
3160
  href: cta.secondary.href,
3104
3161
  children: /* @__PURE__ */ jsx(Button$1, { variant: "outline", className: "w-full", children: cta.secondary.label })
3105
- }),
3106
- cta?.primary && renderLink({
3162
+ }) }),
3163
+ cta?.primary && /* @__PURE__ */ jsx("span", { onClickCapture: closeMobileMenu, children: renderLink({
3107
3164
  href: cta.primary.href,
3108
3165
  children: /* @__PURE__ */ jsx(Button$1, { className: "w-full", children: cta.primary.label })
3109
- })
3166
+ }) })
3110
3167
  ] })
3111
3168
  ] })
3112
3169
  ] })
@@ -3240,7 +3297,7 @@ function LogoStrip({
3240
3297
  ] });
3241
3298
  }
3242
3299
  function GridUnderlay({ variant, size = 24, className, ...props }) {
3243
- const id = React34.useId();
3300
+ const id = React35.useId();
3244
3301
  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: [
3245
3302
  /* @__PURE__ */ jsx("line", { x1: size / 2, y1: 0, x2: size / 2, y2: size, stroke: "currentColor", strokeWidth: 0.5 }),
3246
3303
  /* @__PURE__ */ jsx("line", { x1: 0, y1: size / 2, x2: size, y2: size / 2, stroke: "currentColor", strokeWidth: 0.5 })
@@ -3268,9 +3325,9 @@ function GlowUnderlay({
3268
3325
  style,
3269
3326
  ...props
3270
3327
  }) {
3271
- const [pos, setPos] = React34.useState({ x: "50%", y: "50%" });
3272
- const containerRef = React34.useRef(null);
3273
- React34.useEffect(() => {
3328
+ const [pos, setPos] = React35.useState({ x: "50%", y: "50%" });
3329
+ const containerRef = React35.useRef(null);
3330
+ React35.useEffect(() => {
3274
3331
  if (!followCursor) return;
3275
3332
  const handleMouseMove = (e) => {
3276
3333
  const el = containerRef.current;
@@ -3409,8 +3466,8 @@ function PageTransition({
3409
3466
  style,
3410
3467
  ...props
3411
3468
  }) {
3412
- const [mounted, setMounted] = React34.useState(false);
3413
- React34.useEffect(() => {
3469
+ const [mounted, setMounted] = React35.useState(false);
3470
+ React35.useEffect(() => {
3414
3471
  const id = requestAnimationFrame(() => setMounted(true));
3415
3472
  return () => cancelAnimationFrame(id);
3416
3473
  }, []);
@@ -3485,7 +3542,7 @@ function Sortable({
3485
3542
  if (oldIndex === -1 || newIndex === -1) return;
3486
3543
  onReorder(arrayMove(items, oldIndex, newIndex));
3487
3544
  };
3488
- const childArray = React34.Children.toArray(children);
3545
+ const childArray = React35.Children.toArray(children);
3489
3546
  return /* @__PURE__ */ jsx(DndContext, { sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsx(SortableContext, { items, strategy: strategies[direction], children: /* @__PURE__ */ jsx("div", { className: cn$1(containerClasses[direction], className), children: childArray.map(
3490
3547
  (child, i) => items[i] === void 0 ? null : /* @__PURE__ */ jsx(SortableItem, { id: items[i], disabled, className: itemClassName, children: child }, items[i])
3491
3548
  ) }) }) });
@@ -3497,8 +3554,8 @@ function Spotlight({
3497
3554
  triggerClassName,
3498
3555
  shortcut = "\u2318K"
3499
3556
  }) {
3500
- const [open, setOpen] = React34.useState(false);
3501
- React34.useEffect(() => {
3557
+ const [open, setOpen] = React35.useState(false);
3558
+ React35.useEffect(() => {
3502
3559
  const down = (e) => {
3503
3560
  if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
3504
3561
  e.preventDefault();
@@ -3528,7 +3585,7 @@ function Spotlight({
3528
3585
  /* @__PURE__ */ jsx(CommandInput, { placeholder }),
3529
3586
  /* @__PURE__ */ jsxs(CommandList, { children: [
3530
3587
  /* @__PURE__ */ jsx(CommandEmpty, { children: "No results found." }),
3531
- groups.map((group, i) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
3588
+ groups.map((group, i) => /* @__PURE__ */ jsxs(React35.Fragment, { children: [
3532
3589
  i > 0 && /* @__PURE__ */ jsx(CommandSeparator, {}),
3533
3590
  /* @__PURE__ */ jsx(CommandGroup, { heading: group.heading, children: group.items.map((item) => /* @__PURE__ */ jsxs(
3534
3591
  CommandItem,
@@ -3553,8 +3610,8 @@ function Spotlight({
3553
3610
  var BASE = 40;
3554
3611
  var MAGNIFY_RADIUS = 80;
3555
3612
  function Dock({ items, iconSize = BASE, magnification = 1.7, className, ...props }) {
3556
- const [mouseX, setMouseX] = React34.useState(null);
3557
- const containerRef = React34.useRef(null);
3613
+ const [mouseX, setMouseX] = React35.useState(null);
3614
+ const containerRef = React35.useRef(null);
3558
3615
  function getScale(index) {
3559
3616
  if (mouseX === null || !containerRef.current) return 1;
3560
3617
  const container = containerRef.current;
@@ -3653,7 +3710,7 @@ function ConfirmDialog({ isOpen, options, handleConfirm, handleCancel, destructi
3653
3710
  ] }) });
3654
3711
  }
3655
3712
  function RegisterHotkeys({ hotkeys }) {
3656
- React34.useEffect(() => {
3713
+ React35.useEffect(() => {
3657
3714
  const handleKeyDown = (event) => {
3658
3715
  for (const hotkey of hotkeys) {
3659
3716
  const matches = event.key.toLowerCase() === hotkey.key.toLowerCase() && !!event.ctrlKey === !!hotkey.ctrl && !!event.metaKey === !!hotkey.meta && !!event.shiftKey === !!hotkey.shift && !!event.altKey === !!hotkey.alt;
@@ -3670,10 +3727,10 @@ function RegisterHotkeys({ hotkeys }) {
3670
3727
  return null;
3671
3728
  }
3672
3729
  function useControlledOpen(defaultOpen = false) {
3673
- const [isOpen, setIsOpen] = React34.useState(defaultOpen);
3674
- const open = React34.useCallback(() => setIsOpen(true), []);
3675
- const close = React34.useCallback(() => setIsOpen(false), []);
3676
- const toggle = React34.useCallback(() => setIsOpen((prev) => !prev), []);
3730
+ const [isOpen, setIsOpen] = React35.useState(defaultOpen);
3731
+ const open = React35.useCallback(() => setIsOpen(true), []);
3732
+ const close = React35.useCallback(() => setIsOpen(false), []);
3733
+ const toggle = React35.useCallback(() => setIsOpen((prev) => !prev), []);
3677
3734
  return { isOpen, open, close, toggle, setIsOpen };
3678
3735
  }
3679
3736
  function CommandMenu({
@@ -3686,7 +3743,7 @@ function CommandMenu({
3686
3743
  }) {
3687
3744
  const internal = useControlledOpen(false);
3688
3745
  const isOpen = openProp ?? internal.isOpen;
3689
- const setOpen = React34.useCallback(
3746
+ const setOpen = React35.useCallback(
3690
3747
  (next) => {
3691
3748
  if (openProp === void 0) internal.setIsOpen(next);
3692
3749
  onOpenChange?.(next);
@@ -3711,7 +3768,7 @@ function CommandMenu({
3711
3768
  /* @__PURE__ */ jsx(CommandInput, { placeholder }),
3712
3769
  /* @__PURE__ */ jsxs(CommandList, { children: [
3713
3770
  /* @__PURE__ */ jsx(CommandEmpty, { children: emptyMessage }),
3714
- groups.map((group, i) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
3771
+ groups.map((group, i) => /* @__PURE__ */ jsxs(React35.Fragment, { children: [
3715
3772
  i > 0 && /* @__PURE__ */ jsx(CommandSeparator, {}),
3716
3773
  /* @__PURE__ */ jsx(CommandGroup, { heading: group.heading, children: group.items.map((item) => /* @__PURE__ */ jsxs(
3717
3774
  CommandItem,
@@ -3731,6 +3788,43 @@ function CommandMenu({
3731
3788
  ] })
3732
3789
  ] });
3733
3790
  }
3791
+ function DataView({
3792
+ items,
3793
+ getRowId,
3794
+ renderCard,
3795
+ columns,
3796
+ view,
3797
+ ready = true,
3798
+ gridColumns = 3,
3799
+ gap = "sm",
3800
+ empty,
3801
+ placeholder = null,
3802
+ searchKey,
3803
+ searchPlaceholder,
3804
+ pageSize = 25,
3805
+ emptyMessage,
3806
+ onRowClick,
3807
+ className
3808
+ }) {
3809
+ if (!ready) return /* @__PURE__ */ jsx(Fragment, { children: placeholder });
3810
+ if (items.length === 0 && empty) return /* @__PURE__ */ jsx(Fragment, { children: empty });
3811
+ if (view === "list") {
3812
+ return /* @__PURE__ */ jsx(
3813
+ DataTable,
3814
+ {
3815
+ columns,
3816
+ data: items,
3817
+ searchKey,
3818
+ searchPlaceholder,
3819
+ pageSize,
3820
+ emptyMessage,
3821
+ onRowClick,
3822
+ className
3823
+ }
3824
+ );
3825
+ }
3826
+ return /* @__PURE__ */ jsx(AppGrid, { columns: gridColumns, gap, className: cn$1(className), children: items.map((item) => /* @__PURE__ */ jsx(React35.Fragment, { children: renderCard(item) }, getRowId(item))) });
3827
+ }
3734
3828
  var TOKEN_COLORS = ["var(--chart-1)", "var(--chart-2)", "var(--chart-3)", "var(--chart-4)", "var(--chart-5)"];
3735
3829
  var seriesColor = (s, i) => s.color ?? TOKEN_COLORS[i % TOKEN_COLORS.length];
3736
3830
  function ChartTooltip({
@@ -3903,20 +3997,20 @@ function FileUpload({
3903
3997
  hint,
3904
3998
  className
3905
3999
  }) {
3906
- const [internalFiles, setInternalFiles] = React34.useState(defaultFiles ?? []);
3907
- const [isDragging, setIsDragging] = React34.useState(false);
3908
- const [validationError, setValidationError] = React34.useState(null);
3909
- const inputRef = React34.useRef(null);
3910
- const dragDepth = React34.useRef(0);
4000
+ const [internalFiles, setInternalFiles] = React35.useState(defaultFiles ?? []);
4001
+ const [isDragging, setIsDragging] = React35.useState(false);
4002
+ const [validationError, setValidationError] = React35.useState(null);
4003
+ const inputRef = React35.useRef(null);
4004
+ const dragDepth = React35.useRef(0);
3911
4005
  const files = filesProp ?? internalFiles;
3912
- const setFiles = React34.useCallback(
4006
+ const setFiles = React35.useCallback(
3913
4007
  (next) => {
3914
4008
  if (!filesProp) setInternalFiles(next);
3915
4009
  onFilesChange?.(next);
3916
4010
  },
3917
4011
  [filesProp, onFilesChange]
3918
4012
  );
3919
- const acceptList = React34.useMemo(
4013
+ const acceptList = React35.useMemo(
3920
4014
  () => accept?.split(",").map((a) => a.trim().toLowerCase()).filter(Boolean) ?? [],
3921
4015
  [accept]
3922
4016
  );
@@ -4296,6 +4390,63 @@ function ImageCard({
4296
4390
  }
4297
4391
  );
4298
4392
  }
4393
+ var bannerAspectClass = {
4394
+ wide: "aspect-[3/1]",
4395
+ video: "aspect-video",
4396
+ square: "aspect-square"
4397
+ };
4398
+ function MediaCard({
4399
+ banner,
4400
+ bannerAspect = "wide",
4401
+ footer,
4402
+ children,
4403
+ className,
4404
+ ...props
4405
+ }) {
4406
+ return /* @__PURE__ */ jsxs(
4407
+ Card$1,
4408
+ {
4409
+ className: cn$1("flex h-full w-full flex-col overflow-hidden", banner && "pt-0", className),
4410
+ ...props,
4411
+ children: [
4412
+ banner && /* @__PURE__ */ jsx("div", { className: cn$1("w-full shrink-0 overflow-hidden", bannerAspectClass[bannerAspect]), children: banner }),
4413
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 p-4", children: [
4414
+ children,
4415
+ footer && /* @__PURE__ */ jsx("div", { className: "mt-auto pt-1", children: footer })
4416
+ ] })
4417
+ ]
4418
+ }
4419
+ );
4420
+ }
4421
+ function ViewToggle({ view, onChange, labels, className, ...props }) {
4422
+ const options = [
4423
+ { mode: "cards", label: labels?.cards ?? "Card view", Icon: LayoutGrid },
4424
+ { mode: "list", label: labels?.list ?? "List view", Icon: List }
4425
+ ];
4426
+ return /* @__PURE__ */ jsx(
4427
+ "div",
4428
+ {
4429
+ className: cn$1("inline-flex items-center rounded-md border p-0.5", className),
4430
+ role: "group",
4431
+ "aria-label": "View mode",
4432
+ ...props,
4433
+ children: options.map(({ mode, label, Icon }) => /* @__PURE__ */ jsx(
4434
+ Button$1,
4435
+ {
4436
+ type: "button",
4437
+ size: "sm",
4438
+ variant: view === mode ? "secondary" : "ghost",
4439
+ className: "h-7 px-2",
4440
+ "aria-label": label,
4441
+ "aria-pressed": view === mode,
4442
+ onClick: () => onChange(mode),
4443
+ children: /* @__PURE__ */ jsx(Icon, { className: "size-4" })
4444
+ },
4445
+ mode
4446
+ ))
4447
+ }
4448
+ );
4449
+ }
4299
4450
  var DASH_ON = "5px";
4300
4451
  var DASH_OFF = "4px";
4301
4452
  function FlowConnector({
@@ -4583,7 +4734,7 @@ function PageHeader({
4583
4734
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1 pb-6", className), children: [
4584
4735
  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) => {
4585
4736
  const isLast = i === breadcrumbs.length - 1;
4586
- return /* @__PURE__ */ jsxs(React34.Fragment, { children: [
4737
+ return /* @__PURE__ */ jsxs(React35.Fragment, { children: [
4587
4738
  i > 0 && /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5 shrink-0" }),
4588
4739
  isLast || !crumb.href ? /* @__PURE__ */ jsx("span", { className: cn(isLast && "text-foreground font-medium"), children: crumb.label }) : renderLink({
4589
4740
  href: crumb.href,
@@ -4619,7 +4770,7 @@ function PageHeader({
4619
4770
  ] });
4620
4771
  }
4621
4772
  function Suspensed({ children, fallback }) {
4622
- return /* @__PURE__ */ jsx(React34.Suspense, { fallback: fallback ?? /* @__PURE__ */ jsx(Spinner, {}), children });
4773
+ return /* @__PURE__ */ jsx(React35.Suspense, { fallback: fallback ?? /* @__PURE__ */ jsx(Spinner, {}), children });
4623
4774
  }
4624
4775
  function getInitialTheme() {
4625
4776
  if (typeof document === "undefined") return "dark";
@@ -4627,7 +4778,7 @@ function getInitialTheme() {
4627
4778
  return stored ?? (document.documentElement.classList.contains("dark") ? "dark" : "light");
4628
4779
  }
4629
4780
  function ThemeSwitchMinimal() {
4630
- const [theme, setThemeState] = React34.useState(getInitialTheme);
4781
+ const [theme, setThemeState] = React35.useState(getInitialTheme);
4631
4782
  const toggle = () => {
4632
4783
  const next = theme === "dark" ? "light" : "dark";
4633
4784
  setThemeState(next);
@@ -4753,9 +4904,9 @@ function getInitialColorName() {
4753
4904
  return localStorage.getItem(STORAGE_KEY) ?? "emerald";
4754
4905
  }
4755
4906
  function BrandColorSwitchMinimal() {
4756
- const [active, setActive] = React34.useState(getInitialColorName);
4757
- const [open, setOpen] = React34.useState(false);
4758
- React34.useEffect(() => {
4907
+ const [active, setActive] = React35.useState(getInitialColorName);
4908
+ const [open, setOpen] = React35.useState(false);
4909
+ React35.useEffect(() => {
4759
4910
  const saved = localStorage.getItem(STORAGE_KEY) ?? "emerald";
4760
4911
  const color = BRAND_COLORS.find((c) => c.name === saved);
4761
4912
  if (color) applyColor(color);
@@ -4816,7 +4967,7 @@ function BrandColorSwitchMinimal() {
4816
4967
  ] });
4817
4968
  }
4818
4969
  function ThemeColorUpdater({ colorTheme }) {
4819
- React34.useEffect(() => {
4970
+ React35.useEffect(() => {
4820
4971
  if (colorTheme) {
4821
4972
  document.documentElement.setAttribute("data-theme", colorTheme);
4822
4973
  } else {
@@ -4826,7 +4977,7 @@ function ThemeColorUpdater({ colorTheme }) {
4826
4977
  return null;
4827
4978
  }
4828
4979
  function VersionBanner({ version, message, className, onDismiss }) {
4829
- const [dismissed, setDismissed] = React34.useState(false);
4980
+ const [dismissed, setDismissed] = React35.useState(false);
4830
4981
  if (dismissed) return null;
4831
4982
  return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-4 bg-primary/10 px-4 py-2 text-sm", className), children: [
4832
4983
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -5083,29 +5234,29 @@ function useMounted() {
5083
5234
  return mounted;
5084
5235
  }
5085
5236
  function useConfirm() {
5086
- const [isOpen, setIsOpen] = React34.useState(false);
5087
- const [options, setOptions] = React34.useState({});
5088
- const resolveRef = React34.useRef(null);
5089
- const confirm = React34.useCallback((opts = {}) => {
5237
+ const [isOpen, setIsOpen] = React35.useState(false);
5238
+ const [options, setOptions] = React35.useState({});
5239
+ const resolveRef = React35.useRef(null);
5240
+ const confirm = React35.useCallback((opts = {}) => {
5090
5241
  setOptions(opts);
5091
5242
  setIsOpen(true);
5092
5243
  return new Promise((resolve) => {
5093
5244
  resolveRef.current = resolve;
5094
5245
  });
5095
5246
  }, []);
5096
- const handleConfirm = React34.useCallback(() => {
5247
+ const handleConfirm = React35.useCallback(() => {
5097
5248
  resolveRef.current?.(true);
5098
5249
  setIsOpen(false);
5099
5250
  }, []);
5100
- const handleCancel = React34.useCallback(() => {
5251
+ const handleCancel = React35.useCallback(() => {
5101
5252
  resolveRef.current?.(false);
5102
5253
  setIsOpen(false);
5103
5254
  }, []);
5104
5255
  return { confirm, isOpen, options, handleConfirm, handleCancel };
5105
5256
  }
5106
5257
  function useScrolledPast(threshold) {
5107
- const [scrolledPast, setScrolledPast] = React34.useState(false);
5108
- React34.useEffect(() => {
5258
+ const [scrolledPast, setScrolledPast] = React35.useState(false);
5259
+ React35.useEffect(() => {
5109
5260
  const handleScroll = () => {
5110
5261
  setScrolledPast(window.scrollY > threshold);
5111
5262
  };
@@ -5116,28 +5267,28 @@ function useScrolledPast(threshold) {
5116
5267
  return scrolledPast;
5117
5268
  }
5118
5269
  function useCopyToClipboard() {
5119
- const [copied, setCopied] = React34.useState(false);
5120
- const timeoutRef = React34.useRef(void 0);
5121
- const copy = React34.useCallback((text) => {
5270
+ const [copied, setCopied] = React35.useState(false);
5271
+ const timeoutRef = React35.useRef(void 0);
5272
+ const copy = React35.useCallback((text) => {
5122
5273
  navigator.clipboard.writeText(text).then(() => {
5123
5274
  setCopied(true);
5124
5275
  clearTimeout(timeoutRef.current);
5125
5276
  timeoutRef.current = setTimeout(() => setCopied(false), 2e3);
5126
5277
  });
5127
5278
  }, []);
5128
- React34.useEffect(() => () => clearTimeout(timeoutRef.current), []);
5279
+ React35.useEffect(() => () => clearTimeout(timeoutRef.current), []);
5129
5280
  return [copied, copy];
5130
5281
  }
5131
5282
  function useDebounce(value, delay) {
5132
- const [debounced, setDebounced] = React34.useState(value);
5133
- React34.useEffect(() => {
5283
+ const [debounced, setDebounced] = React35.useState(value);
5284
+ React35.useEffect(() => {
5134
5285
  const timer = setTimeout(() => setDebounced(value), delay);
5135
5286
  return () => clearTimeout(timer);
5136
5287
  }, [value, delay]);
5137
5288
  return debounced;
5138
5289
  }
5139
5290
  function useLocalStorage(key, initialValue) {
5140
- const [storedValue, setStoredValue] = React34.useState(() => {
5291
+ const [storedValue, setStoredValue] = React35.useState(() => {
5141
5292
  if (typeof window === "undefined") return initialValue;
5142
5293
  try {
5143
5294
  const item = window.localStorage.getItem(key);
@@ -5146,7 +5297,7 @@ function useLocalStorage(key, initialValue) {
5146
5297
  return initialValue;
5147
5298
  }
5148
5299
  });
5149
- const setValue = React34.useCallback(
5300
+ const setValue = React35.useCallback(
5150
5301
  (value) => {
5151
5302
  try {
5152
5303
  setStoredValue(value);
@@ -5160,12 +5311,19 @@ function useLocalStorage(key, initialValue) {
5160
5311
  );
5161
5312
  return [storedValue, setValue];
5162
5313
  }
5314
+
5315
+ // src/hooks/use-view-mode.ts
5316
+ function useViewMode(key = "view-mode", fallback = "cards") {
5317
+ const [stored, setStored] = useLocalStorage(key, fallback);
5318
+ const mounted = useMounted();
5319
+ return { view: mounted ? stored : fallback, setView: setStored, ready: mounted };
5320
+ }
5163
5321
  function useMediaQuery(query) {
5164
- const [matches, setMatches] = React34.useState(() => {
5322
+ const [matches, setMatches] = React35.useState(() => {
5165
5323
  if (typeof window === "undefined") return false;
5166
5324
  return window.matchMedia(query).matches;
5167
5325
  });
5168
- React34.useEffect(() => {
5326
+ React35.useEffect(() => {
5169
5327
  if (typeof window === "undefined") return;
5170
5328
  const mql = window.matchMedia(query);
5171
5329
  setMatches(mql.matches);
@@ -5176,18 +5334,18 @@ function useMediaQuery(query) {
5176
5334
  return matches;
5177
5335
  }
5178
5336
  function usePagination(total, pageSize) {
5179
- const [page, setPage] = React34.useState(1);
5337
+ const [page, setPage] = React35.useState(1);
5180
5338
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
5181
5339
  const offset = (page - 1) * pageSize;
5182
- const goTo = React34.useCallback(
5340
+ const goTo = React35.useCallback(
5183
5341
  (p) => setPage(Math.min(Math.max(1, p), totalPages)),
5184
5342
  [totalPages]
5185
5343
  );
5186
- const next = React34.useCallback(() => goTo(page + 1), [page, goTo]);
5187
- const prev = React34.useCallback(() => goTo(page - 1), [page, goTo]);
5344
+ const next = React35.useCallback(() => goTo(page + 1), [page, goTo]);
5345
+ const prev = React35.useCallback(() => goTo(page - 1), [page, goTo]);
5188
5346
  return { page, pageSize, totalPages, offset, hasPrev: page > 1, hasNext: page < totalPages, goTo, next, prev };
5189
5347
  }
5190
5348
 
5191
- export { ActivityFeed, AnimatedPill, AppContent, AppGrid, AppGridCell, AppScreen, AppShell, AuthSection, Badge, BillingPanel, BrandColorSwitchMinimal, Button, Card, Carousel, ChangelogCard, ChangelogList, Chart, Checkbox, CommandMenu, ComparisonSection, ConfirmDialog, ContactSection, ContextMenu, CountUp, CountdownTimer, 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, NotificationsPopover, OlwibaUIProvider, OnboardingWizard, Overlay, PageHeader, PageTransition, PhoneFrame, PostCard, PostList, PricingCard, PricingSection, PublicPageFrame, QualificationSection, RegisterHotkeys, RootErrorFallback, Section, SectionTitle, SettingsSection, Sortable, Spotlight, Stack, StaggerChildren, StatCard, StatsSection, StepsSection, Suspensed, Switch, TeamMembersPanel, TeamSection, TechStackSection, TestimonialCard, TestimonialsSection, Textarea, ThemeColorUpdater, ThemeSwitchMinimal, Underlay, UpdateBanner, UpgradePrompt, VersionBanner, marketingSectionSpacing, notify, useConfirm, useControlledOpen, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMediaQuery, useMounted, useOlwibaUI, usePagination, useScrolledPast, useUIMode };
5349
+ export { ActivityFeed, AnimatedPill, AppContent, AppGrid, AppGridCell, AppScreen, AppShell, AuthSection, Badge, BillingPanel, BrandColorSwitchMinimal, Button, Card, Carousel, ChangelogCard, ChangelogList, Chart, Checkbox, CommandMenu, ComparisonSection, ConfirmDialog, ContactSection, ContextMenu, CountUp, CountdownTimer, CtaSection, DataTable, DataView, DevBanner, Dock, EmptyState, ErrorPage, FadeIn, FaqSection, FeatureCard, FeatureMarqueeSection, FeaturesSection, FileUpload, FlowBracket, FlowConnector, Footer, FullPageSpinner, GlassCard, Grid, GridItem, GroupedFeaturesSection, HeroSection, ImageCard, Input, LogoStrip, MediaCard, ModeSwitchMinimal, Navbar, NewsletterSection, NotificationToast, NotificationsPopover, OlwibaUIProvider, OnboardingWizard, Overlay, PageHeader, PageTransition, PhoneFrame, PostCard, PostList, PricingCard, PricingSection, PublicPageFrame, QualificationSection, RegisterHotkeys, RootErrorFallback, Section, SectionTitle, SettingsSection, Sortable, Spotlight, Stack, StaggerChildren, StatCard, StatsSection, StepsSection, Suspensed, Switch, TeamMembersPanel, TeamSection, TechStackSection, TestimonialCard, TestimonialsSection, Textarea, ThemeColorUpdater, ThemeSwitchMinimal, Underlay, UpdateBanner, UpgradePrompt, VersionBanner, ViewToggle, marketingSectionSpacing, notify, useConfirm, useControlledOpen, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMediaQuery, useMounted, useOlwibaUI, usePagination, useScrolledPast, useUIMode, useViewMode };
5192
5350
  //# sourceMappingURL=index.js.map
5193
5351
  //# sourceMappingURL=index.js.map