blodemd 0.0.11 → 0.0.13

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 (86) hide show
  1. package/README.md +11 -47
  2. package/dev-server/app/layout.tsx +1 -1
  3. package/dev-server/next.config.js +19 -9
  4. package/dev-server/tsconfig.json +0 -3
  5. package/dist/cli.mjs +732 -123
  6. package/dist/cli.mjs.map +1 -1
  7. package/docs/app/globals.css +15 -1
  8. package/docs/components/api/api-playground.tsx +2 -2
  9. package/docs/components/docs/copy-page-menu.tsx +55 -27
  10. package/docs/components/docs/doc-header.tsx +1 -1
  11. package/docs/components/docs/doc-shell.tsx +89 -88
  12. package/docs/components/docs/doc-sidebar.tsx +6 -3
  13. package/docs/components/docs/doc-toc.tsx +1 -1
  14. package/docs/components/docs/mobile-nav.tsx +8 -16
  15. package/docs/components/docs/sidebar-scroll-area.tsx +58 -0
  16. package/docs/components/git/repo-picker.tsx +526 -0
  17. package/docs/components/mdx/agent-instructions.tsx +17 -0
  18. package/docs/components/mdx/code-block.tsx +6 -1
  19. package/docs/components/mdx/code-group.tsx +1 -1
  20. package/docs/components/mdx/iframe.tsx +62 -0
  21. package/docs/components/mdx/index.tsx +4 -0
  22. package/docs/components/mdx/tabs.tsx +5 -5
  23. package/docs/components/mdx/video.tsx +45 -12
  24. package/docs/components/third-parties.tsx +29 -0
  25. package/docs/components/ui/badge.tsx +61 -0
  26. package/docs/components/ui/breadcrumb.tsx +61 -41
  27. package/docs/components/ui/button-group.tsx +83 -0
  28. package/docs/components/ui/button.tsx +30 -55
  29. package/docs/components/ui/command.tsx +32 -4
  30. package/docs/components/ui/copy-button.tsx +12 -19
  31. package/docs/components/ui/dialog.tsx +50 -1
  32. package/docs/components/ui/input.tsx +16 -97
  33. package/docs/components/ui/kbd.tsx +98 -0
  34. package/docs/components/ui/morph-icon.tsx +79 -0
  35. package/docs/components/ui/popover.tsx +225 -30
  36. package/docs/components/ui/search.tsx +0 -9
  37. package/docs/components/ui/sheet.tsx +30 -1
  38. package/docs/components/ui/sidebar.tsx +332 -7
  39. package/docs/components/ui/site-footer.tsx +6 -4
  40. package/docs/components/ui/skeleton.tsx +11 -0
  41. package/docs/components/ui/switch.tsx +32 -0
  42. package/docs/components/ui/tabs.tsx +138 -0
  43. package/docs/lib/api-client.ts +72 -0
  44. package/docs/lib/contextual-options.ts +9 -0
  45. package/docs/lib/dashboard-session.ts +167 -0
  46. package/docs/lib/db.ts +13 -0
  47. package/docs/lib/env.ts +4 -3
  48. package/docs/lib/etag.ts +22 -0
  49. package/docs/lib/github-install.ts +33 -0
  50. package/docs/lib/project-authz.ts +46 -0
  51. package/docs/lib/routes.ts +5 -1
  52. package/docs/lib/supabase.ts +30 -6
  53. package/docs/lib/tenancy.ts +1 -0
  54. package/docs/lib/tenant-static.ts +206 -4
  55. package/docs/lib/tenants.ts +5 -1
  56. package/docs/lib/time-ago.ts +24 -0
  57. package/docs/lib/use-tab-observer.ts +71 -0
  58. package/package.json +3 -1
  59. package/packages/@repo/common/package.json +2 -2
  60. package/packages/@repo/contracts/dist/git.d.ts +28 -0
  61. package/packages/@repo/contracts/dist/git.d.ts.map +1 -0
  62. package/packages/@repo/contracts/dist/git.js +24 -0
  63. package/packages/@repo/contracts/dist/index.d.ts +1 -1
  64. package/packages/@repo/contracts/dist/index.d.ts.map +1 -1
  65. package/packages/@repo/contracts/dist/index.js +1 -1
  66. package/packages/@repo/contracts/package.json +2 -2
  67. package/packages/@repo/contracts/src/git.ts +31 -0
  68. package/packages/@repo/contracts/src/index.ts +1 -1
  69. package/packages/@repo/models/dist/docs-config.d.ts +6 -0
  70. package/packages/@repo/models/dist/docs-config.d.ts.map +1 -1
  71. package/packages/@repo/models/dist/docs-config.js +1 -0
  72. package/packages/@repo/models/package.json +2 -2
  73. package/packages/@repo/models/src/docs-config.ts +1 -0
  74. package/packages/@repo/prebuild/package.json +2 -2
  75. package/packages/@repo/previewing/dist/index.d.ts +3 -0
  76. package/packages/@repo/previewing/dist/index.d.ts.map +1 -1
  77. package/packages/@repo/previewing/dist/index.js +48 -0
  78. package/packages/@repo/previewing/package.json +2 -2
  79. package/packages/@repo/previewing/src/index.ts +56 -0
  80. package/packages/@repo/validation/package.json +2 -2
  81. package/packages/@repo/validation/src/blodemd-docs-schema.json +1 -0
  82. package/scripts/prepare-package.mjs +14 -0
  83. package/packages/@repo/contracts/dist/api-key.d.ts +0 -30
  84. package/packages/@repo/contracts/dist/api-key.d.ts.map +0 -1
  85. package/packages/@repo/contracts/dist/api-key.js +0 -20
  86. package/packages/@repo/contracts/src/api-key.ts +0 -27
@@ -4,6 +4,7 @@ import { XIcon } from "blode-icons-react";
4
4
  import { Dialog as DialogPrimitive } from "radix-ui";
5
5
  import * as React from "react";
6
6
 
7
+ import { Button } from "@/components/ui/button";
7
8
  import { cn } from "@/lib/utils";
8
9
 
9
10
  const Dialog = ({
@@ -12,12 +13,24 @@ const Dialog = ({
12
13
  <DialogPrimitive.Root data-slot="dialog" {...props} />
13
14
  );
14
15
 
16
+ const DialogTrigger = ({
17
+ ...props
18
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) => (
19
+ <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
20
+ );
21
+
15
22
  const DialogPortal = ({
16
23
  ...props
17
24
  }: React.ComponentProps<typeof DialogPrimitive.Portal>) => (
18
25
  <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
19
26
  );
20
27
 
28
+ const DialogClose = ({
29
+ ...props
30
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) => (
31
+ <DialogPrimitive.Close data-slot="dialog-close" {...props} />
32
+ );
33
+
21
34
  const DialogOverlay = ({
22
35
  className,
23
36
  ...props
@@ -72,6 +85,31 @@ const DialogHeader = ({ className, ...props }: React.ComponentProps<"div">) => (
72
85
  />
73
86
  );
74
87
 
88
+ const DialogFooter = ({
89
+ className,
90
+ showCloseButton = false,
91
+ children,
92
+ ...props
93
+ }: React.ComponentProps<"div"> & {
94
+ showCloseButton?: boolean;
95
+ }) => (
96
+ <div
97
+ data-slot="dialog-footer"
98
+ className={cn(
99
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
100
+ className
101
+ )}
102
+ {...props}
103
+ >
104
+ {children}
105
+ {showCloseButton && (
106
+ <DialogPrimitive.Close asChild>
107
+ <Button variant="outline">Close</Button>
108
+ </DialogPrimitive.Close>
109
+ )}
110
+ </div>
111
+ );
112
+
75
113
  const DialogTitle = ({
76
114
  className,
77
115
  ...props
@@ -94,4 +132,15 @@ const DialogDescription = ({
94
132
  />
95
133
  );
96
134
 
97
- export { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle };
135
+ export {
136
+ Dialog,
137
+ DialogClose,
138
+ DialogContent,
139
+ DialogDescription,
140
+ DialogFooter,
141
+ DialogHeader,
142
+ DialogOverlay,
143
+ DialogPortal,
144
+ DialogTitle,
145
+ DialogTrigger,
146
+ };
@@ -1,104 +1,23 @@
1
- "use client";
2
-
3
- import { CircleXFilledIcon } from "blode-icons-react";
4
1
  import * as React from "react";
5
2
 
6
3
  import { cn } from "@/lib/utils";
7
4
 
8
- export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
9
- clearable?: boolean;
10
- clearClassName?: string;
11
- hasError?: boolean;
12
- leftAddon?: React.ReactNode | null;
13
- leftControl?: React.ReactNode | null;
14
- onClear?: () => void;
15
- rightAddon?: React.ReactNode | null;
16
- rightControl?: React.ReactNode | null;
17
- }
18
-
19
- const Input = React.forwardRef<HTMLInputElement, InputProps>(
20
- (
21
- {
22
- className,
23
- clearClassName,
24
- hasError,
25
- clearable,
26
- onClear,
27
- leftAddon,
28
- rightAddon,
29
- leftControl,
30
- rightControl,
31
- ...props
32
- },
33
- ref
34
- ) => (
35
- <label
36
- className={cn("relative w-full", {
37
- "input-group": !!leftAddon || !!rightAddon,
38
- })}
39
- >
40
- {leftAddon && (
41
- <span className="shrink-0 cursor-pointer">{leftAddon}</span>
42
- )}
43
-
44
- {leftControl && (
45
- <div className="absolute top-0 left-0 flex h-full flex-row place-items-center items-center justify-center">
46
- {leftControl}
47
- </div>
48
- )}
49
-
50
- <div className="w-full">
51
- <input
52
- className={cn(
53
- "input flex h-[var(--field-height)] w-full rounded-[var(--field-radius)] border border-input bg-card px-[var(--field-padding-x)] py-[var(--field-padding-y)] font-normal font-sans text-base text-foreground leading-snug shadow-input transition-colors placeholder:text-placeholder-foreground hover:border-input-hover focus:border-ring focus:outline-hidden focus:ring-2 focus:ring-ring/15 focus:ring-offset-1 focus:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
54
- {
55
- "border-destructive-foreground": hasError,
56
- "hover:border-input! focus:border-input!": props.readOnly,
57
- "pr-12": clearable && !!props.value && rightControl,
58
- "pr-9": clearable && !!props.value,
59
- },
60
- className
61
- )}
62
- ref={ref}
63
- {...props}
64
- />
65
-
66
- {clearable && !!props.value && (
67
- <div className="absolute top-0 right-0 flex flex-row gap-1 pr-3">
68
- <button
69
- aria-label="clear input"
70
- className={cn(
71
- "flex h-[var(--field-height)] cursor-pointer items-center justify-center p-0! text-muted-foreground",
72
- clearClassName
73
- )}
74
- onClick={onClear}
75
- type="button"
76
- >
77
- <CircleXFilledIcon className="size-5 text-muted-foreground/50" />
78
- </button>
79
- </div>
80
- )}
81
- </div>
82
-
83
- {rightControl && (
84
- <div
85
- className={cn(
86
- "absolute top-0 right-0 flex h-full flex-row place-items-center items-center justify-center",
87
- {
88
- "right-9": clearable && !!props.value,
89
- }
90
- )}
91
- >
92
- {rightControl}
93
- </div>
94
- )}
95
-
96
- {rightAddon && (
97
- <span className="shrink-0 cursor-pointer">{rightAddon}</span>
98
- )}
99
- </label>
100
- )
5
+ const Input = ({
6
+ className,
7
+ type,
8
+ ...props
9
+ }: React.ComponentProps<"input">) => (
10
+ <input
11
+ type={type}
12
+ data-slot="input"
13
+ className={cn(
14
+ "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
15
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
16
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
101
21
  );
102
- Input.displayName = "Input";
103
22
 
104
23
  export { Input };
@@ -0,0 +1,98 @@
1
+ import {
2
+ ArrowCornerDownLeftIcon,
3
+ ArrowDownIcon,
4
+ ArrowLeftXIcon,
5
+ ArrowUpIcon,
6
+ ArrowWall2RightIcon,
7
+ CmdIcon,
8
+ ControlIcon,
9
+ OptIcon,
10
+ ShiftIcon,
11
+ } from "blode-icons-react";
12
+ import { cva } from "class-variance-authority";
13
+ import type { VariantProps } from "class-variance-authority";
14
+ import * as React from "react";
15
+
16
+ import { cn } from "@/lib/utils";
17
+
18
+ type KbdIcon =
19
+ | "mod"
20
+ | "shift"
21
+ | "enter"
22
+ | "command"
23
+ | "ctrl"
24
+ | "alt"
25
+ | "tab"
26
+ | "backspace"
27
+ | "up"
28
+ | "down";
29
+
30
+ const kbdVariants = cva(
31
+ "pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm px-1.5 font-medium font-sans text-xs ring-1 ring-inset [&_svg:not([class*='size-'])]:size-3",
32
+ {
33
+ defaultVariants: {
34
+ variant: "default",
35
+ },
36
+ variants: {
37
+ variant: {
38
+ default:
39
+ "bg-muted text-muted-foreground ring-border [[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background [[data-slot=tooltip-content]_&]:ring-background/20 dark:[[data-slot=tooltip-content]_&]:bg-background/10 dark:[[data-slot=tooltip-content]_&]:ring-background/10",
40
+ tooltip:
41
+ "bg-background/20 text-background ring-background/20 dark:bg-background/10 dark:ring-background/10",
42
+ },
43
+ },
44
+ }
45
+ );
46
+
47
+ interface KbdProps
48
+ extends
49
+ React.ComponentPropsWithoutRef<"kbd">,
50
+ VariantProps<typeof kbdVariants> {
51
+ icon?: KbdIcon;
52
+ }
53
+
54
+ const iconMap: Record<KbdIcon, React.ReactNode> = {
55
+ alt: <OptIcon className="size-3" />,
56
+ backspace: <ArrowLeftXIcon className="size-3" />,
57
+ command: <CmdIcon className="size-3" />,
58
+ ctrl: <ControlIcon className="size-3" />,
59
+ down: <ArrowDownIcon className="size-3" />,
60
+ enter: <ArrowCornerDownLeftIcon className="size-3" />,
61
+ mod: <CmdIcon className="size-3" />,
62
+ shift: <ShiftIcon className="size-3" />,
63
+ tab: <ArrowWall2RightIcon className="size-3" />,
64
+ up: <ArrowUpIcon className="size-3" />,
65
+ };
66
+
67
+ const Kbd = React.forwardRef<React.ElementRef<"kbd">, KbdProps>(
68
+ ({ className, variant, children, icon, ...props }, ref) => {
69
+ const content = icon ? iconMap[icon] : children;
70
+
71
+ return (
72
+ <kbd
73
+ className={cn(kbdVariants({ variant }), className)}
74
+ data-slot="kbd"
75
+ ref={ref}
76
+ {...props}
77
+ >
78
+ {content}
79
+ </kbd>
80
+ );
81
+ }
82
+ );
83
+ Kbd.displayName = "Kbd";
84
+
85
+ const KbdGroup = React.forwardRef<
86
+ React.ElementRef<"kbd">,
87
+ React.ComponentPropsWithoutRef<"kbd">
88
+ >(({ className, ...props }, ref) => (
89
+ <kbd
90
+ className={cn("inline-flex items-center gap-1", className)}
91
+ data-slot="kbd-group"
92
+ ref={ref}
93
+ {...props}
94
+ />
95
+ ));
96
+ KbdGroup.displayName = "KbdGroup";
97
+
98
+ export { Kbd, KbdGroup };
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ import { motion, useReducedMotion } from "motion/react";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ type MorphIconName = "cross" | "menu";
8
+
9
+ interface MorphIconProps extends React.SVGAttributes<SVGSVGElement> {
10
+ icon: MorphIconName;
11
+ size?: number;
12
+ strokeWidth?: number;
13
+ }
14
+
15
+ const ICONS = {
16
+ cross: {
17
+ opacity: [1, 1, 0] as const,
18
+ paths: ["M2.5 2.5L11.5 11.5", "M11.5 2.5L2.5 11.5", "M7 7L7 7"] as const,
19
+ },
20
+ menu: {
21
+ opacity: [1, 1, 1] as const,
22
+ paths: ["M1 2.5L13 2.5", "M1 7L13 7", "M1 11.5L13 11.5"] as const,
23
+ },
24
+ } as const;
25
+
26
+ const transition = {
27
+ duration: 0.3,
28
+ ease: [0.4, 0, 0.2, 1] as const,
29
+ };
30
+
31
+ const instantTransition = { duration: 0 };
32
+
33
+ export const MorphIcon = ({
34
+ icon,
35
+ size = 32,
36
+ strokeWidth = 2,
37
+ className,
38
+ style,
39
+ ...props
40
+ }: MorphIconProps) => {
41
+ const prefersReducedMotion = useReducedMotion();
42
+ const def = ICONS[icon];
43
+ const [p0, p1, p2] = def.paths;
44
+ const [o0, o1, o2] = def.opacity;
45
+ const t = prefersReducedMotion ? instantTransition : transition;
46
+
47
+ return (
48
+ <svg
49
+ aria-hidden="true"
50
+ className={cn("text-current", className)}
51
+ fill="none"
52
+ height={size}
53
+ stroke="currentColor"
54
+ strokeLinecap="round"
55
+ strokeWidth={strokeWidth}
56
+ style={style}
57
+ viewBox="0 0 14 14"
58
+ width={size}
59
+ xmlns="http://www.w3.org/2000/svg"
60
+ {...props}
61
+ >
62
+ <motion.path
63
+ initial={false}
64
+ animate={{ d: p0, opacity: o0 }}
65
+ transition={t}
66
+ />
67
+ <motion.path
68
+ initial={false}
69
+ animate={{ d: p1, opacity: o1 }}
70
+ transition={t}
71
+ />
72
+ <motion.path
73
+ initial={false}
74
+ animate={{ d: p2, opacity: o2 }}
75
+ transition={t}
76
+ />
77
+ </svg>
78
+ );
79
+ };
@@ -1,15 +1,92 @@
1
1
  "use client";
2
2
 
3
3
  import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
4
- import type * as React from "react";
4
+ import * as React from "react";
5
5
 
6
6
  import { cn } from "@/lib/utils";
7
7
 
8
+ type PopoverInteractOutsideEvent = Event & {
9
+ preventDefault: () => void;
10
+ };
11
+
12
+ interface PopoverContextType {
13
+ anchor: HTMLElement | null;
14
+ setAnchor: (anchor: HTMLElement | null) => void;
15
+ setOnInteractOutside: (
16
+ handler?: (event: PopoverInteractOutsideEvent) => void
17
+ ) => void;
18
+ }
19
+
20
+ const PopoverContext = React.createContext<PopoverContextType | undefined>(
21
+ undefined
22
+ );
23
+
24
+ const usePopoverContext = () => React.useContext(PopoverContext);
25
+
26
+ const setRef = <T,>(ref: React.Ref<T> | undefined, value: T) => {
27
+ if (typeof ref === "function") {
28
+ ref(value);
29
+ return;
30
+ }
31
+
32
+ if (ref && "current" in ref) {
33
+ ref.current = value;
34
+ }
35
+ };
36
+
8
37
  const Popover = ({
38
+ onOpenChange,
9
39
  ...props
10
- }: React.ComponentProps<typeof PopoverPrimitive.Root>) => (
11
- <PopoverPrimitive.Root data-slot="popover" {...props} />
12
- );
40
+ }: React.ComponentProps<typeof PopoverPrimitive.Root>) => {
41
+ const [anchor, setAnchor] = React.useState<HTMLElement | null>(null);
42
+ const onInteractOutsideRef = React.useRef<
43
+ ((event: PopoverInteractOutsideEvent) => void) | null
44
+ >(null);
45
+
46
+ const handleOpenChange = React.useCallback<
47
+ NonNullable<
48
+ React.ComponentProps<typeof PopoverPrimitive.Root>["onOpenChange"]
49
+ >
50
+ >(
51
+ (nextOpen, eventDetails) => {
52
+ if (!nextOpen && eventDetails.reason === "outside-press") {
53
+ const interactHandler = onInteractOutsideRef.current;
54
+
55
+ if (interactHandler) {
56
+ const wrappedEvent = Object.create(
57
+ eventDetails.event
58
+ ) as PopoverInteractOutsideEvent;
59
+ wrappedEvent.preventDefault = () => eventDetails.cancel();
60
+ interactHandler(wrappedEvent);
61
+ }
62
+ }
63
+
64
+ onOpenChange?.(nextOpen, eventDetails);
65
+ },
66
+ [onOpenChange]
67
+ );
68
+
69
+ const contextValue = React.useMemo<PopoverContextType>(
70
+ () => ({
71
+ anchor,
72
+ setAnchor,
73
+ setOnInteractOutside: (handler) => {
74
+ onInteractOutsideRef.current = handler ?? null;
75
+ },
76
+ }),
77
+ [anchor]
78
+ );
79
+
80
+ return (
81
+ <PopoverContext.Provider value={contextValue}>
82
+ <PopoverPrimitive.Root
83
+ data-slot="popover"
84
+ onOpenChange={handleOpenChange}
85
+ {...props}
86
+ />
87
+ </PopoverContext.Provider>
88
+ );
89
+ };
13
90
 
14
91
  const PopoverTrigger = ({
15
92
  asChild = false,
@@ -19,7 +96,7 @@ const PopoverTrigger = ({
19
96
  asChild?: boolean;
20
97
  }) => {
21
98
  const render =
22
- asChild && children && typeof children === "object"
99
+ asChild && React.isValidElement(children)
23
100
  ? (children as React.ReactElement)
24
101
  : undefined;
25
102
 
@@ -34,39 +111,157 @@ const PopoverTrigger = ({
34
111
  );
35
112
  };
36
113
 
114
+ type PopoverContentProps = React.ComponentProps<typeof PopoverPrimitive.Popup> &
115
+ Pick<
116
+ React.ComponentProps<typeof PopoverPrimitive.Positioner>,
117
+ "align" | "alignOffset" | "side" | "sideOffset"
118
+ > & {
119
+ asChild?: boolean;
120
+ onInteractOutside?: (event: PopoverInteractOutsideEvent) => void;
121
+ onOpenAutoFocus?: (event: Event) => void;
122
+ };
123
+
37
124
  const PopoverContent = ({
125
+ asChild = false,
38
126
  children,
39
127
  className,
40
128
  align = "center",
41
129
  alignOffset = 0,
42
130
  side = "bottom",
43
131
  sideOffset = 4,
132
+ initialFocus,
133
+ onInteractOutside,
134
+ onOpenAutoFocus,
44
135
  ...props
45
- }: React.ComponentProps<typeof PopoverPrimitive.Popup> &
46
- Pick<
47
- React.ComponentProps<typeof PopoverPrimitive.Positioner>,
48
- "align" | "alignOffset" | "side" | "sideOffset"
49
- >) => (
50
- <PopoverPrimitive.Portal>
51
- <PopoverPrimitive.Positioner
52
- align={align}
53
- alignOffset={alignOffset}
54
- className="isolate z-50"
55
- side={side}
56
- sideOffset={sideOffset}
57
- >
58
- <PopoverPrimitive.Popup
59
- className={cn(
60
- "z-50 w-72 origin-(--transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-soft outline-hidden data-closed:animate-out data-open:animate-in data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
61
- className
62
- )}
63
- data-slot="popover-content"
64
- {...props}
136
+ }: PopoverContentProps) => {
137
+ const popoverContext = usePopoverContext();
138
+
139
+ React.useEffect(() => {
140
+ popoverContext?.setOnInteractOutside(onInteractOutside);
141
+
142
+ return () => {
143
+ popoverContext?.setOnInteractOutside(undefined);
144
+ };
145
+ }, [onInteractOutside, popoverContext]);
146
+
147
+ React.useEffect(() => {
148
+ if (!onOpenAutoFocus) {
149
+ return;
150
+ }
151
+
152
+ const event = new Event("openAutoFocus", { cancelable: true });
153
+ onOpenAutoFocus(event);
154
+ }, [onOpenAutoFocus]);
155
+
156
+ const render =
157
+ asChild && React.isValidElement(children)
158
+ ? (children as React.ReactElement)
159
+ : undefined;
160
+
161
+ return (
162
+ <PopoverPrimitive.Portal>
163
+ <PopoverPrimitive.Positioner
164
+ align={align}
165
+ alignOffset={alignOffset}
166
+ anchor={popoverContext?.anchor}
167
+ className="isolate z-50"
168
+ side={side}
169
+ sideOffset={sideOffset}
65
170
  >
66
- {children}
67
- </PopoverPrimitive.Popup>
68
- </PopoverPrimitive.Positioner>
69
- </PopoverPrimitive.Portal>
171
+ <PopoverPrimitive.Popup
172
+ className={cn(
173
+ "data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-soft outline-hidden data-closed:animate-out data-open:animate-in",
174
+ className
175
+ )}
176
+ data-slot="popover-content"
177
+ initialFocus={onOpenAutoFocus ? false : initialFocus}
178
+ render={render}
179
+ {...props}
180
+ >
181
+ {asChild ? null : children}
182
+ </PopoverPrimitive.Popup>
183
+ </PopoverPrimitive.Positioner>
184
+ </PopoverPrimitive.Portal>
185
+ );
186
+ };
187
+
188
+ const PopoverAnchor = ({
189
+ asChild = false,
190
+ children,
191
+ ref,
192
+ ...props
193
+ }: React.ComponentProps<"div"> & {
194
+ asChild?: boolean;
195
+ }) => {
196
+ const popoverContext = usePopoverContext();
197
+
198
+ const handleRef = React.useCallback(
199
+ (node: HTMLElement | null) => {
200
+ popoverContext?.setAnchor(node);
201
+ setRef(ref as React.Ref<HTMLElement> | undefined, node);
202
+ },
203
+ [popoverContext, ref]
204
+ );
205
+
206
+ if (asChild && React.isValidElement(children)) {
207
+ const child = children as React.ReactElement<{
208
+ ref?: React.Ref<HTMLElement>;
209
+ [key: string]: unknown;
210
+ }>;
211
+
212
+ // oxlint-disable-next-line eslint-plugin-react/no-clone-element -- asChild escape hatch
213
+ return React.cloneElement(child, {
214
+ ...props,
215
+ ref: (node: HTMLElement | null) => {
216
+ setRef(child.props.ref, node);
217
+ handleRef(node);
218
+ },
219
+ });
220
+ }
221
+
222
+ return (
223
+ <div data-slot="popover-anchor" ref={handleRef} {...props}>
224
+ {children}
225
+ </div>
226
+ );
227
+ };
228
+
229
+ const PopoverHeader = ({
230
+ className,
231
+ ...props
232
+ }: React.ComponentProps<"div">) => (
233
+ <div
234
+ className={cn("flex flex-col gap-1 text-sm", className)}
235
+ data-slot="popover-header"
236
+ {...props}
237
+ />
238
+ );
239
+
240
+ const PopoverTitle = ({ className, ...props }: React.ComponentProps<"h2">) => (
241
+ <div
242
+ className={cn("font-medium", className)}
243
+ data-slot="popover-title"
244
+ {...props}
245
+ />
246
+ );
247
+
248
+ const PopoverDescription = ({
249
+ className,
250
+ ...props
251
+ }: React.ComponentProps<"p">) => (
252
+ <p
253
+ className={cn("text-muted-foreground", className)}
254
+ data-slot="popover-description"
255
+ {...props}
256
+ />
70
257
  );
71
258
 
72
- export { Popover, PopoverTrigger, PopoverContent };
259
+ export {
260
+ Popover,
261
+ PopoverTrigger,
262
+ PopoverContent,
263
+ PopoverAnchor,
264
+ PopoverHeader,
265
+ PopoverTitle,
266
+ PopoverDescription,
267
+ };
@@ -319,11 +319,6 @@ export const Search = ({ basePath }: { basePath: string }) => {
319
319
  [closeSearch]
320
320
  );
321
321
 
322
- const handleOpenAutoFocus = useCallback((event: Event) => {
323
- event.preventDefault();
324
- inputRef.current?.focus();
325
- }, []);
326
-
327
322
  const warmSearch = useCallback(async () => {
328
323
  try {
329
324
  await loadSearchItems();
@@ -442,15 +437,11 @@ export const Search = ({ basePath }: { basePath: string }) => {
442
437
  >
443
438
  <span className="hidden lg:inline-flex">Search documentation...</span>
444
439
  <span className="inline-flex lg:hidden">Search...</span>
445
- <span className="ml-auto hidden pr-3 text-[11px] text-muted-foreground sm:inline-flex">
446
- Cmd K
447
- </span>
448
440
  </button>
449
441
  <Dialog onOpenChange={handleOpenChange} open={open}>
450
442
  <DialogContent
451
443
  className="max-w-2xl gap-0 overflow-hidden p-0"
452
444
  onKeyDown={handleDialogKeyDown}
453
- onOpenAutoFocus={handleOpenAutoFocus}
454
445
  showCloseButton={false}
455
446
  >
456
447
  <DialogTitle className="sr-only">Search documentation</DialogTitle>