blodemd 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +11 -47
  2. package/dev-server/app/layout.tsx +1 -1
  3. package/dist/cli.mjs +1078 -406
  4. package/dist/cli.mjs.map +1 -1
  5. package/docs/app/globals.css +15 -1
  6. package/docs/components/api/api-playground.tsx +2 -2
  7. package/docs/components/docs/copy-page-menu.tsx +55 -27
  8. package/docs/components/docs/doc-header.tsx +1 -1
  9. package/docs/components/docs/doc-shell.tsx +89 -88
  10. package/docs/components/docs/doc-sidebar.tsx +6 -3
  11. package/docs/components/docs/doc-toc.tsx +1 -1
  12. package/docs/components/docs/mobile-nav.tsx +8 -16
  13. package/docs/components/docs/sidebar-scroll-area.tsx +58 -0
  14. package/docs/components/git/repo-picker.tsx +526 -0
  15. package/docs/components/mdx/agent-instructions.tsx +17 -0
  16. package/docs/components/mdx/code-block.tsx +6 -1
  17. package/docs/components/mdx/code-group.tsx +1 -1
  18. package/docs/components/mdx/iframe.tsx +62 -0
  19. package/docs/components/mdx/index.tsx +4 -0
  20. package/docs/components/mdx/tabs.tsx +5 -5
  21. package/docs/components/mdx/video.tsx +45 -12
  22. package/docs/components/third-parties.tsx +29 -0
  23. package/docs/components/ui/badge.tsx +61 -0
  24. package/docs/components/ui/breadcrumb.tsx +61 -41
  25. package/docs/components/ui/button-group.tsx +83 -0
  26. package/docs/components/ui/button.tsx +30 -55
  27. package/docs/components/ui/command.tsx +32 -4
  28. package/docs/components/ui/copy-button.tsx +12 -19
  29. package/docs/components/ui/dialog.tsx +50 -1
  30. package/docs/components/ui/input.tsx +16 -97
  31. package/docs/components/ui/kbd.tsx +98 -0
  32. package/docs/components/ui/morph-icon.tsx +79 -0
  33. package/docs/components/ui/popover.tsx +225 -30
  34. package/docs/components/ui/search.tsx +0 -9
  35. package/docs/components/ui/sheet.tsx +30 -1
  36. package/docs/components/ui/sidebar.tsx +332 -7
  37. package/docs/components/ui/site-footer.tsx +6 -4
  38. package/docs/components/ui/skeleton.tsx +11 -0
  39. package/docs/components/ui/switch.tsx +32 -0
  40. package/docs/components/ui/tabs.tsx +138 -0
  41. package/docs/lib/api-client.ts +72 -0
  42. package/docs/lib/contextual-options.ts +9 -0
  43. package/docs/lib/dashboard-session.ts +167 -0
  44. package/docs/lib/db.ts +13 -0
  45. package/docs/lib/env.ts +4 -3
  46. package/docs/lib/etag.ts +22 -0
  47. package/docs/lib/github-install.ts +33 -0
  48. package/docs/lib/project-authz.ts +46 -0
  49. package/docs/lib/routes.ts +5 -1
  50. package/docs/lib/supabase.ts +30 -6
  51. package/docs/lib/tenancy.ts +1 -0
  52. package/docs/lib/tenant-static.ts +206 -4
  53. package/docs/lib/tenants.ts +5 -1
  54. package/docs/lib/time-ago.ts +24 -0
  55. package/docs/lib/use-tab-observer.ts +71 -0
  56. package/package.json +2 -2
  57. package/packages/@repo/contracts/dist/git.d.ts +28 -0
  58. package/packages/@repo/contracts/dist/git.d.ts.map +1 -0
  59. package/packages/@repo/contracts/dist/git.js +24 -0
  60. package/packages/@repo/contracts/dist/index.d.ts +1 -1
  61. package/packages/@repo/contracts/dist/index.d.ts.map +1 -1
  62. package/packages/@repo/contracts/dist/index.js +1 -1
  63. package/packages/@repo/contracts/src/git.ts +31 -0
  64. package/packages/@repo/contracts/src/index.ts +1 -1
  65. package/packages/@repo/models/dist/docs-config.d.ts +9 -0
  66. package/packages/@repo/models/dist/docs-config.d.ts.map +1 -1
  67. package/packages/@repo/models/dist/docs-config.js +7 -0
  68. package/packages/@repo/models/src/docs-config.ts +7 -0
  69. package/packages/@repo/previewing/dist/index.d.ts +4 -0
  70. package/packages/@repo/previewing/dist/index.d.ts.map +1 -1
  71. package/packages/@repo/previewing/dist/index.js +53 -2
  72. package/packages/@repo/previewing/src/index.ts +64 -2
  73. package/packages/@repo/validation/src/blodemd-docs-schema.json +8 -1
  74. package/scripts/prepare-package.mjs +14 -0
  75. package/packages/@repo/contracts/dist/api-key.d.ts +0 -30
  76. package/packages/@repo/contracts/dist/api-key.d.ts.map +0 -1
  77. package/packages/@repo/contracts/dist/api-key.js +0 -20
  78. package/packages/@repo/contracts/src/api-key.ts +0 -27
@@ -12,6 +12,18 @@ const Sheet = ({
12
12
  <SheetPrimitive.Root data-slot="sheet" {...props} />
13
13
  );
14
14
 
15
+ const SheetTrigger = ({
16
+ ...props
17
+ }: React.ComponentProps<typeof SheetPrimitive.Trigger>) => (
18
+ <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
19
+ );
20
+
21
+ const SheetClose = ({
22
+ ...props
23
+ }: React.ComponentProps<typeof SheetPrimitive.Close>) => (
24
+ <SheetPrimitive.Close data-slot="sheet-close" {...props} />
25
+ );
26
+
15
27
  const SheetPortal = ({
16
28
  ...props
17
29
  }: React.ComponentProps<typeof SheetPrimitive.Portal>) => (
@@ -79,6 +91,14 @@ const SheetHeader = ({ className, ...props }: React.ComponentProps<"div">) => (
79
91
  />
80
92
  );
81
93
 
94
+ const SheetFooter = ({ className, ...props }: React.ComponentProps<"div">) => (
95
+ <div
96
+ data-slot="sheet-footer"
97
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
98
+ {...props}
99
+ />
100
+ );
101
+
82
102
  const SheetTitle = ({
83
103
  className,
84
104
  ...props
@@ -101,4 +121,13 @@ const SheetDescription = ({
101
121
  />
102
122
  );
103
123
 
104
- export { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription };
124
+ export {
125
+ Sheet,
126
+ SheetTrigger,
127
+ SheetClose,
128
+ SheetContent,
129
+ SheetHeader,
130
+ SheetFooter,
131
+ SheetTitle,
132
+ SheetDescription,
133
+ };
@@ -1,7 +1,13 @@
1
1
  "use client";
2
+ // oxlint-disable eslint-plugin-unicorn/no-document-cookie -- sidebar state persisted via cookie (by design)
3
+ // oxlint-disable eslint/no-shadow -- registry code; upstream uses `open` in nested scopes
4
+ // oxlint-disable eslint/no-param-reassign -- registry code normalises tooltip prop
5
+ // oxlint-disable eslint-plugin-react/button-has-type -- trigger intentionally uses default button behavior
6
+ // oxlint-disable eslint-plugin-react-perf/jsx-no-new-function-as-prop -- registry-defined trigger composition
2
7
 
3
8
  import { mergeProps } from "@base-ui/react/merge-props";
4
9
  import { useRender } from "@base-ui/react/use-render";
10
+ import { PanelLeftIcon } from "blode-icons-react";
5
11
  import { cva } from "class-variance-authority";
6
12
  import type { VariantProps } from "class-variance-authority";
7
13
  import type * as React from "react";
@@ -14,6 +20,9 @@ import {
14
20
  useState,
15
21
  } from "react";
16
22
 
23
+ import { Button } from "@/components/ui/button";
24
+ import { Input } from "@/components/ui/input";
25
+ import { Separator } from "@/components/ui/separator";
17
26
  import {
18
27
  Sheet,
19
28
  SheetContent,
@@ -21,6 +30,7 @@ import {
21
30
  SheetHeader,
22
31
  SheetTitle,
23
32
  } from "@/components/ui/sheet";
33
+ import { Skeleton } from "@/components/ui/skeleton";
24
34
  import {
25
35
  Tooltip,
26
36
  TooltipContent,
@@ -88,7 +98,6 @@ const SidebarProvider = ({
88
98
  }
89
99
 
90
100
  // This sets the cookie to keep the sidebar state.
91
- // oxlint-disable-next-line eslint-plugin-unicorn/no-document-cookie
92
101
  document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
93
102
  },
94
103
  [setOpenProp, open]
@@ -97,9 +106,7 @@ const SidebarProvider = ({
97
106
  // Helper to toggle the sidebar.
98
107
  const toggleSidebar = useCallback(
99
108
  () =>
100
- isMobile
101
- ? setOpenMobile((prevOpen) => !prevOpen)
102
- : setOpen((prevOpen) => !prevOpen),
109
+ isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open),
103
110
  [isMobile, setOpen]
104
111
  );
105
112
 
@@ -263,6 +270,123 @@ const Sidebar = ({
263
270
  );
264
271
  };
265
272
 
273
+ const SidebarTrigger = ({
274
+ className,
275
+ onClick,
276
+ ...props
277
+ }: React.ComponentProps<typeof Button>) => {
278
+ const { toggleSidebar } = useSidebar();
279
+
280
+ return (
281
+ <Button
282
+ className={cn("size-7", className)}
283
+ data-sidebar="trigger"
284
+ data-slot="sidebar-trigger"
285
+ onClick={(event) => {
286
+ onClick?.(event);
287
+ toggleSidebar();
288
+ }}
289
+ size="icon"
290
+ variant="ghost"
291
+ {...props}
292
+ >
293
+ <PanelLeftIcon />
294
+ <span className="sr-only">Toggle Sidebar</span>
295
+ </Button>
296
+ );
297
+ };
298
+
299
+ const SidebarRail = ({
300
+ className,
301
+ ...props
302
+ }: React.ComponentProps<"button">) => {
303
+ const { toggleSidebar } = useSidebar();
304
+
305
+ return (
306
+ <button
307
+ aria-label="Toggle Sidebar"
308
+ className={cn(
309
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
310
+ "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
311
+ "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
312
+ "group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full",
313
+ "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
314
+ "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
315
+ className
316
+ )}
317
+ data-sidebar="rail"
318
+ data-slot="sidebar-rail"
319
+ onClick={toggleSidebar}
320
+ tabIndex={-1}
321
+ title="Toggle Sidebar"
322
+ {...props}
323
+ />
324
+ );
325
+ };
326
+
327
+ const SidebarInset = ({
328
+ className,
329
+ ...props
330
+ }: React.ComponentProps<"main">) => (
331
+ <main
332
+ className={cn(
333
+ "relative flex w-full flex-1 flex-col bg-background",
334
+ "md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm",
335
+ className
336
+ )}
337
+ data-slot="sidebar-inset"
338
+ {...props}
339
+ />
340
+ );
341
+
342
+ const SidebarInput = ({
343
+ className,
344
+ ...props
345
+ }: React.ComponentProps<typeof Input>) => (
346
+ <Input
347
+ className={cn("h-8 w-full bg-background shadow-none", className)}
348
+ data-sidebar="input"
349
+ data-slot="sidebar-input"
350
+ {...props}
351
+ />
352
+ );
353
+
354
+ const SidebarHeader = ({
355
+ className,
356
+ ...props
357
+ }: React.ComponentProps<"div">) => (
358
+ <div
359
+ className={cn("flex flex-col gap-2 p-2", className)}
360
+ data-sidebar="header"
361
+ data-slot="sidebar-header"
362
+ {...props}
363
+ />
364
+ );
365
+
366
+ const SidebarFooter = ({
367
+ className,
368
+ ...props
369
+ }: React.ComponentProps<"div">) => (
370
+ <div
371
+ className={cn("flex flex-col gap-2 p-2", className)}
372
+ data-sidebar="footer"
373
+ data-slot="sidebar-footer"
374
+ {...props}
375
+ />
376
+ );
377
+
378
+ const SidebarSeparator = ({
379
+ className,
380
+ ...props
381
+ }: React.ComponentProps<typeof Separator>) => (
382
+ <Separator
383
+ className={cn("mx-2 w-auto bg-sidebar-border", className)}
384
+ data-sidebar="separator"
385
+ data-slot="sidebar-separator"
386
+ {...props}
387
+ />
388
+ );
389
+
266
390
  const SidebarContent = ({
267
391
  className,
268
392
  ...props
@@ -312,6 +436,33 @@ const SidebarGroupLabel = ({
312
436
  },
313
437
  });
314
438
 
439
+ const SidebarGroupAction = ({
440
+ className,
441
+ asChild = false,
442
+ children,
443
+ ...props
444
+ }: React.ComponentProps<"button"> & { asChild?: boolean }) =>
445
+ useRender({
446
+ defaultTagName: "button",
447
+ props: mergeProps<"button">(
448
+ {
449
+ className: cn(
450
+ "absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
451
+ // Increases the hit area of the button on mobile.
452
+ "after:absolute after:-inset-2 md:after:hidden",
453
+ "group-data-[collapsible=icon]:hidden",
454
+ className
455
+ ),
456
+ },
457
+ asChild ? props : { ...props, children }
458
+ ),
459
+ render: asChild ? (children as React.ReactElement) : undefined,
460
+ state: {
461
+ sidebar: "group-action",
462
+ slot: "sidebar-group-action",
463
+ },
464
+ });
465
+
315
466
  const SidebarGroupContent = ({
316
467
  className,
317
468
  ...props
@@ -404,8 +555,11 @@ const SidebarMenuButton = ({
404
555
  return button;
405
556
  }
406
557
 
407
- const tooltipProps =
408
- typeof tooltip === "string" ? { children: tooltip } : tooltip;
558
+ if (typeof tooltip === "string") {
559
+ tooltip = {
560
+ children: tooltip,
561
+ };
562
+ }
409
563
 
410
564
  return (
411
565
  <Tooltip>
@@ -414,20 +568,191 @@ const SidebarMenuButton = ({
414
568
  align="center"
415
569
  hidden={state !== "collapsed" || isMobile}
416
570
  side="right"
417
- {...tooltipProps}
571
+ {...tooltip}
418
572
  />
419
573
  </Tooltip>
420
574
  );
421
575
  };
422
576
 
577
+ const SidebarMenuAction = ({
578
+ className,
579
+ asChild = false,
580
+ showOnHover = false,
581
+ children,
582
+ ...props
583
+ }: React.ComponentProps<"button"> & {
584
+ asChild?: boolean;
585
+ showOnHover?: boolean;
586
+ }) =>
587
+ useRender({
588
+ defaultTagName: "button",
589
+ props: mergeProps<"button">(
590
+ {
591
+ className: cn(
592
+ "absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
593
+ // Increases the hit area of the button on mobile.
594
+ "after:absolute after:-inset-2 md:after:hidden",
595
+ "peer-data-[size=sm]/menu-button:top-1",
596
+ "peer-data-[size=default]/menu-button:top-1.5",
597
+ "peer-data-[size=lg]/menu-button:top-2.5",
598
+ "group-data-[collapsible=icon]:hidden",
599
+ showOnHover &&
600
+ "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active]/menu-button:text-sidebar-accent-foreground md:opacity-0",
601
+ className
602
+ ),
603
+ },
604
+ asChild ? props : { ...props, children }
605
+ ),
606
+ render: asChild ? (children as React.ReactElement) : undefined,
607
+ state: {
608
+ sidebar: "menu-action",
609
+ slot: "sidebar-menu-action",
610
+ },
611
+ });
612
+
613
+ const SidebarMenuBadge = ({
614
+ className,
615
+ ...props
616
+ }: React.ComponentProps<"div">) => (
617
+ <div
618
+ className={cn(
619
+ "pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums",
620
+ "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active]/menu-button:text-sidebar-accent-foreground",
621
+ "peer-data-[size=sm]/menu-button:top-1",
622
+ "peer-data-[size=default]/menu-button:top-1.5",
623
+ "peer-data-[size=lg]/menu-button:top-2.5",
624
+ "group-data-[collapsible=icon]:hidden",
625
+ className
626
+ )}
627
+ data-sidebar="menu-badge"
628
+ data-slot="sidebar-menu-badge"
629
+ {...props}
630
+ />
631
+ );
632
+
633
+ const SidebarMenuSkeleton = ({
634
+ className,
635
+ showIcon = false,
636
+ ...props
637
+ }: React.ComponentProps<"div"> & {
638
+ showIcon?: boolean;
639
+ }) => {
640
+ // Random width between 50 to 90%.
641
+ const width = useMemo(() => `${Math.floor(Math.random() * 40) + 50}%`, []);
642
+
643
+ return (
644
+ <div
645
+ className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
646
+ data-sidebar="menu-skeleton"
647
+ data-slot="sidebar-menu-skeleton"
648
+ {...props}
649
+ >
650
+ {showIcon && (
651
+ <Skeleton
652
+ className="size-4 rounded-md"
653
+ data-sidebar="menu-skeleton-icon"
654
+ />
655
+ )}
656
+ <Skeleton
657
+ className="h-4 max-w-(--skeleton-width) flex-1"
658
+ data-sidebar="menu-skeleton-text"
659
+ style={
660
+ {
661
+ "--skeleton-width": width,
662
+ } as React.CSSProperties
663
+ }
664
+ />
665
+ </div>
666
+ );
667
+ };
668
+
669
+ const SidebarMenuSub = ({
670
+ className,
671
+ ...props
672
+ }: React.ComponentProps<"ul">) => (
673
+ <ul
674
+ className={cn(
675
+ "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5",
676
+ "group-data-[collapsible=icon]:hidden",
677
+ className
678
+ )}
679
+ data-sidebar="menu-sub"
680
+ data-slot="sidebar-menu-sub"
681
+ {...props}
682
+ />
683
+ );
684
+
685
+ const SidebarMenuSubItem = ({
686
+ className,
687
+ ...props
688
+ }: React.ComponentProps<"li">) => (
689
+ <li
690
+ className={cn("group/menu-sub-item relative", className)}
691
+ data-sidebar="menu-sub-item"
692
+ data-slot="sidebar-menu-sub-item"
693
+ {...props}
694
+ />
695
+ );
696
+
697
+ const SidebarMenuSubButton = ({
698
+ asChild = false,
699
+ size = "md",
700
+ isActive = false,
701
+ className,
702
+ children,
703
+ ...props
704
+ }: React.ComponentProps<"a"> & {
705
+ asChild?: boolean;
706
+ size?: "sm" | "md";
707
+ isActive?: boolean;
708
+ }) =>
709
+ useRender({
710
+ defaultTagName: "a",
711
+ props: mergeProps<"a">(
712
+ {
713
+ className: cn(
714
+ "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
715
+ "data-[active]:bg-sidebar-accent data-[active]:text-sidebar-accent-foreground",
716
+ size === "sm" && "text-xs",
717
+ size === "md" && "text-sm",
718
+ "group-data-[collapsible=icon]:hidden",
719
+ className
720
+ ),
721
+ },
722
+ asChild ? props : { ...props, children }
723
+ ),
724
+ render: asChild ? (children as React.ReactElement) : undefined,
725
+ state: {
726
+ active: isActive,
727
+ sidebar: "menu-sub-button",
728
+ size,
729
+ slot: "sidebar-menu-sub-button",
730
+ },
731
+ });
732
+
423
733
  export {
424
734
  Sidebar,
425
735
  SidebarContent,
736
+ SidebarFooter,
426
737
  SidebarGroup,
738
+ SidebarGroupAction,
427
739
  SidebarGroupContent,
428
740
  SidebarGroupLabel,
741
+ SidebarHeader,
742
+ SidebarInput,
743
+ SidebarInset,
429
744
  SidebarMenu,
745
+ SidebarMenuAction,
746
+ SidebarMenuBadge,
430
747
  SidebarMenuButton,
431
748
  SidebarMenuItem,
749
+ SidebarMenuSkeleton,
750
+ SidebarMenuSub,
751
+ SidebarMenuSubButton,
752
+ SidebarMenuSubItem,
432
753
  SidebarProvider,
754
+ SidebarRail,
755
+ SidebarSeparator,
756
+ SidebarTrigger,
757
+ useSidebar,
433
758
  };
@@ -1,9 +1,10 @@
1
1
  import Image from "next/image";
2
2
 
3
+ import { Badge } from "@/components/ui/badge";
3
4
  import { siteConfig } from "@/lib/config";
4
5
 
5
6
  export const SiteFooter = () => (
6
- <footer className="flex flex-col items-center justify-center gap-2 pt-16 pb-8 text-muted-foreground text-sm">
7
+ <footer className="flex flex-col items-center justify-center gap-3 pt-16 pb-8 text-muted-foreground text-sm">
7
8
  <div className="flex items-center gap-1">
8
9
  Crafted by
9
10
  <a
@@ -23,9 +24,10 @@ export const SiteFooter = () => (
23
24
  Matthew Blode
24
25
  </a>
25
26
  </div>
26
- <div className="flex items-center gap-2 text-muted-foreground/30">
27
- <span className="text-muted-foreground">v{siteConfig.version}</span>{" "}
28
- &bull;
27
+ <div className="flex items-center gap-2">
28
+ <Badge className="font-mono" variant="outline">
29
+ v{siteConfig.version}
30
+ </Badge>
29
31
  <a
30
32
  className="text-muted-foreground transition-colors hover:text-foreground"
31
33
  href={siteConfig.links.github}
@@ -0,0 +1,11 @@
1
+ import { cn } from "@/lib/utils";
2
+
3
+ const Skeleton = ({ className, ...props }: React.ComponentProps<"div">) => (
4
+ <div
5
+ data-slot="skeleton"
6
+ className={cn("animate-pulse rounded-md bg-accent", className)}
7
+ {...props}
8
+ />
9
+ );
10
+
11
+ export { Skeleton };
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch";
4
+ import * as React from "react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const Switch = React.forwardRef<
9
+ React.ElementRef<typeof SwitchPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SwitchPrimitive.Root
13
+ className={cn(
14
+ "peer inline-flex h-[24px] w-[42px] shrink-0 cursor-pointer items-center rounded-full transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background data-disabled:cursor-not-allowed data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-50 data-unchecked:hover:bg-input-hover",
15
+ className
16
+ )}
17
+ data-slot="switch"
18
+ ref={ref}
19
+ {...props}
20
+ >
21
+ <SwitchPrimitive.Thumb
22
+ className={cn(
23
+ "pointer-events-none block size-[18px] rounded-full bg-card shadow-lg ring-0 transition-transform data-checked:translate-x-[21px] data-unchecked:translate-x-[3px] dark:bg-card-foreground"
24
+ )}
25
+ data-slot="switch-thumb"
26
+ />
27
+ </SwitchPrimitive.Root>
28
+ ));
29
+
30
+ Switch.displayName = SwitchPrimitive.Root.displayName;
31
+
32
+ export { Switch };
@@ -0,0 +1,138 @@
1
+ "use client";
2
+
3
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
4
+ import { cva } from "class-variance-authority";
5
+ import type { VariantProps } from "class-variance-authority";
6
+ import mergeRefs from "merge-refs";
7
+ import * as React from "react";
8
+
9
+ import { useTabObserver } from "@/hooks/use-tab-observer";
10
+ import { cn } from "@/lib/utils";
11
+
12
+ const Tabs = ({
13
+ className,
14
+ orientation = "horizontal",
15
+ ...props
16
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) => (
17
+ <TabsPrimitive.Root
18
+ className={cn("group/tabs flex gap-2 data-horizontal:flex-col", className)}
19
+ data-orientation={orientation}
20
+ data-slot="tabs"
21
+ orientation={orientation}
22
+ {...props}
23
+ />
24
+ );
25
+
26
+ const tabsListVariants = cva(
27
+ "group/tabs-list relative isolate inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground data-[variant=line]:rounded-none group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
28
+ {
29
+ defaultVariants: {
30
+ variant: "default",
31
+ },
32
+ variants: {
33
+ variant: {
34
+ default: "bg-muted",
35
+ line: "gap-1 bg-transparent",
36
+ },
37
+ },
38
+ }
39
+ );
40
+
41
+ const TabsList = ({
42
+ className,
43
+ variant = "default",
44
+ ref,
45
+ ...props
46
+ }: React.ComponentProps<typeof TabsPrimitive.List> &
47
+ VariantProps<typeof tabsListVariants>) => {
48
+ const [indicatorStyle, setIndicatorStyle] = React.useState({
49
+ height: 0,
50
+ left: 0,
51
+ top: 0,
52
+ width: 0,
53
+ });
54
+ const [hasIndicatorPosition, setHasIndicatorPosition] = React.useState(false);
55
+ const [canAnimateIndicator, setCanAnimateIndicator] = React.useState(false);
56
+ const hasInitializedIndicator = React.useRef(false);
57
+ const { listRef } = useTabObserver({
58
+ onActiveTabChange: (_, activeTab) => {
59
+ setIndicatorStyle({
60
+ height: activeTab.offsetHeight,
61
+ left: activeTab.offsetLeft,
62
+ top: activeTab.offsetTop,
63
+ width: activeTab.offsetWidth,
64
+ });
65
+
66
+ if (!hasInitializedIndicator.current) {
67
+ hasInitializedIndicator.current = true;
68
+ setHasIndicatorPosition(true);
69
+ requestAnimationFrame(() => {
70
+ setCanAnimateIndicator(true);
71
+ });
72
+ return;
73
+ }
74
+
75
+ setHasIndicatorPosition(true);
76
+ },
77
+ });
78
+
79
+ return (
80
+ <TabsPrimitive.List
81
+ className={cn(tabsListVariants({ variant }), className)}
82
+ data-slot="tabs-list"
83
+ data-variant={variant}
84
+ ref={mergeRefs(ref, listRef)}
85
+ {...props}
86
+ >
87
+ {variant === "default" ? (
88
+ <span
89
+ aria-hidden="true"
90
+ className={cn(
91
+ "pointer-events-none absolute top-0 left-0 z-0 rounded-md bg-background shadow-sm",
92
+ canAnimateIndicator
93
+ ? "transition-[width,height,transform,opacity] duration-300"
94
+ : "transition-none",
95
+ hasIndicatorPosition ? "opacity-100" : "opacity-0"
96
+ )}
97
+ style={{
98
+ height: `${indicatorStyle.height}px`,
99
+ transform: `translate(${indicatorStyle.left}px, ${indicatorStyle.top}px)`,
100
+ transitionTimingFunction: "cubic-bezier(0.65, 0, 0.35, 1)",
101
+ width: `${indicatorStyle.width}px`,
102
+ }}
103
+ />
104
+ ) : null}
105
+ {props.children}
106
+ </TabsPrimitive.List>
107
+ );
108
+ };
109
+
110
+ const TabsTrigger = ({
111
+ className,
112
+ ...props
113
+ }: React.ComponentProps<typeof TabsPrimitive.Tab>) => (
114
+ <TabsPrimitive.Tab
115
+ className={cn(
116
+ "relative z-10 inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-1.5 py-0.5 font-medium text-foreground/60 text-sm transition-all hover:text-foreground focus-visible:border-ring focus-visible:outline-1 focus-visible:outline-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-[variant=line]/tabs-list:data-active:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
117
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
118
+ "data-active:text-foreground group-data-[variant=default]/tabs-list:data-active:bg-transparent dark:data-active:text-foreground group-data-[variant=default]/tabs-list:dark:data-active:border-transparent group-data-[variant=default]/tabs-list:dark:data-active:bg-transparent",
119
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
120
+ className
121
+ )}
122
+ data-slot="tabs-trigger"
123
+ {...props}
124
+ />
125
+ );
126
+
127
+ const TabsContent = ({
128
+ className,
129
+ ...props
130
+ }: React.ComponentProps<typeof TabsPrimitive.Panel>) => (
131
+ <TabsPrimitive.Panel
132
+ className={cn("flex-1 text-sm outline-none", className)}
133
+ data-slot="tabs-content"
134
+ {...props}
135
+ />
136
+ );
137
+
138
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };