@plannotator/ui 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import React, { useRef, useState, useEffect, useMemo, forwardRef, useImperativeHandle, useCallback } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import hljs from 'highlight.js';
4
- import { Block, Annotation, AnnotationType, EditorMode, type InputMethod, type ImageAttachment, type ActionsLabelMode } from '../types';
5
- import { Frontmatter, computeListIndices, groupBlocks } from '../utils/parser';
4
+ import { AnnotationType, type Block, type Annotation, type EditorMode, type InputMethod, type ImageAttachment, type ActionsLabelMode } from '../types';
5
+ import { computeListIndices, groupBlocks, type Frontmatter } from '../utils/parser';
6
6
  import { buildHeadingSlugMap } from '../utils/slugify';
7
7
  import { BlockRenderer } from './BlockRenderer';
8
8
  import { CodeBlock } from './blocks/CodeBlock';
@@ -11,7 +11,6 @@ import { TableToolbar } from './blocks/TableToolbar';
11
11
  import { TablePopout } from './blocks/TablePopout';
12
12
  import { CodePathValidationContext } from './CodePathValidationContext';
13
13
  import { useValidatedCodePaths } from '../hooks/useValidatedCodePaths';
14
- import { ListMarker } from './ListMarker';
15
14
  import { AnnotationToolbar } from './AnnotationToolbar';
16
15
  import { FloatingQuickLabelPicker } from './FloatingQuickLabelPicker';
17
16
 
@@ -33,13 +32,12 @@ class ToolbarErrorBoundary extends React.Component<
33
32
  }
34
33
  }
35
34
 
36
- import { CommentPopover, type CommentAskAIContext, type CommentAskAIHandler } from './CommentPopover';
35
+ import { CommentPopover, type CommentAskAIHandler } from './CommentPopover';
37
36
  import { TaterSpriteSitting } from './TaterSpriteSitting';
38
37
  import { AttachmentsButton } from './AttachmentsButton';
39
38
  import { MessagesIcon } from './icons/MessagesIcon';
40
39
  import { GraphvizBlock } from './GraphvizBlock';
41
40
  import { MermaidBlock } from './MermaidBlock';
42
- import { getImageSrc } from './ImageThumbnail';
43
41
  import { isGraphvizLanguage, isMermaidLanguage } from './diagramLanguages';
44
42
  import { getIdentity } from '../utils/identity';
45
43
  import { type QuickLabel } from '../utils/quickLabels';
@@ -112,6 +110,15 @@ interface ViewerProps {
112
110
  onToggleCheckbox?: (blockId: string, checked: boolean) => void;
113
111
  checkboxOverrides?: Map<string, boolean>;
114
112
  onAskAI?: CommentAskAIHandler;
113
+ /** Whether comment popovers offer image attachments. Hosts without an
114
+ * uploadTransport pass false so the attach affordance never dead-ends.
115
+ * Default true — today's behavior. */
116
+ allowImages?: boolean;
117
+ /** View-only mode: suppresses every annotation-creation entry point
118
+ * (selection toolbar, comment popovers, quick labels, pinpoint, global
119
+ * comment, attachments, checkbox toggles). Existing annotations still
120
+ * render and remain selectable. Default false — today's behavior. */
121
+ readOnly?: boolean;
115
122
  }
116
123
 
117
124
  export interface ViewerHandle {
@@ -191,6 +198,8 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
191
198
  onToggleCheckbox,
192
199
  checkboxOverrides,
193
200
  onAskAI,
201
+ allowImages = true,
202
+ readOnly = false,
194
203
  }, ref) => {
195
204
  const [copied, setCopied] = useState(false);
196
205
  const [lightbox, setLightbox] = useState<{ src: string; alt: string } | null>(null);
@@ -283,6 +292,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
283
292
  onSelectAnnotation,
284
293
  selectedAnnotationId,
285
294
  mode,
295
+ enabled: !readOnly,
286
296
  });
287
297
 
288
298
  // Refs for code block annotation path
@@ -319,7 +329,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
319
329
  containerRef,
320
330
  highlighterRef,
321
331
  inputMethod,
322
- enabled: !toolbarState && !hookCommentPopover && !viewerCommentPopover && !hookQuickLabelPicker && !codeBlockQuickLabelPicker && !(isPlanDiffActive ?? false),
332
+ enabled: !readOnly && !toolbarState && !hookCommentPopover && !viewerCommentPopover && !hookQuickLabelPicker && !codeBlockQuickLabelPicker && !(isPlanDiffActive ?? false),
323
333
  onCodeBlockClick: handlePinpointCodeBlockClick,
324
334
  });
325
335
 
@@ -612,7 +622,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
612
622
  )}
613
623
 
614
624
  {/* Attachments button */}
615
- {onAddGlobalAttachment && onRemoveGlobalAttachment && (
625
+ {!readOnly && onAddGlobalAttachment && onRemoveGlobalAttachment && (
616
626
  <AttachmentsButton
617
627
  images={globalAttachments}
618
628
  onAdd={onAddGlobalAttachment}
@@ -623,6 +633,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
623
633
  )}
624
634
 
625
635
  {/* <span className="md:hidden">Comment</span><span className="hidden md:inline">Global comment</span> button */}
636
+ {!readOnly && (
626
637
  <button
627
638
  ref={globalCommentButtonRef}
628
639
  onClick={() => {
@@ -641,6 +652,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
641
652
  {actionsLabelMode === 'full' && <span>Global comment</span>}
642
653
  {actionsLabelMode === 'short' && <span>Comment</span>}
643
654
  </button>
655
+ )}
644
656
 
645
657
  {/* Copy plan/file button */}
646
658
  <button
@@ -683,7 +695,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
683
695
  orderedIndex={indices[i]}
684
696
  onOpenLinkedDoc={onOpenLinkedDoc}
685
697
  onOpenCodeFile={onOpenCodeFile}
686
- onToggleCheckbox={onToggleCheckbox}
698
+ onToggleCheckbox={readOnly ? undefined : onToggleCheckbox}
687
699
  checkboxOverrides={checkboxOverrides}
688
700
  githubRepo={repoInfo?.display}
689
701
  headingAnchorId={headingSlugMap.get(block.id)}
@@ -758,7 +770,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
758
770
  isHovered={inputMethod !== 'pinpoint' && hoveredCodeBlock?.block.id === group.block.id}
759
771
  />
760
772
  ) : (
761
- <BlockRenderer imageBaseDir={imageBaseDir} onImageClick={(src, alt) => setLightbox({ src, alt })} key={group.block.id} block={group.block} onOpenLinkedDoc={onOpenLinkedDoc} onOpenCodeFile={onOpenCodeFile} onNavigateAnchor={scrollToAnchor} onToggleCheckbox={onToggleCheckbox} checkboxOverrides={checkboxOverrides} githubRepo={repoInfo?.display} headingAnchorId={headingSlugMap.get(group.block.id)} />
773
+ <BlockRenderer imageBaseDir={imageBaseDir} onImageClick={(src, alt) => setLightbox({ src, alt })} key={group.block.id} block={group.block} onOpenLinkedDoc={onOpenLinkedDoc} onOpenCodeFile={onOpenCodeFile} onNavigateAnchor={scrollToAnchor} onToggleCheckbox={readOnly ? undefined : onToggleCheckbox} checkboxOverrides={checkboxOverrides} githubRepo={repoInfo?.display} headingAnchorId={headingSlugMap.get(group.block.id)} />
762
774
  )
763
775
  )}
764
776
 
@@ -875,6 +887,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
875
887
  initialText={hookCommentPopover.initialText}
876
888
  onSubmit={hookCommentSubmit}
877
889
  onClose={hookCommentClose}
890
+ allowImages={allowImages}
878
891
  onAskAI={onAskAI}
879
892
  askAIContext={{
880
893
  kind: 'selection',
@@ -892,6 +905,7 @@ export const Viewer = forwardRef<ViewerHandle, ViewerProps>(({
892
905
  initialText={viewerCommentPopover.initialText}
893
906
  onSubmit={handleViewerCommentSubmit}
894
907
  onClose={handleViewerCommentClose}
908
+ allowImages={allowImages}
895
909
  onAskAI={onAskAI}
896
910
  askAIContext={{
897
911
  kind: viewerCommentPopover.isGlobal ? 'general' : 'selection',
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useRef, useEffect, useCallback } from 'react';
2
2
  import hljs from 'highlight.js';
3
3
  import 'highlight.js/styles/github-dark.css';
4
- import { Block } from '../../types';
4
+ import type { Block } from '../../types';
5
5
 
6
6
  interface CodeBlockProps {
7
7
  block: Block;
@@ -10,7 +10,7 @@ interface CodeBlockProps {
10
10
  isHovered: boolean;
11
11
  }
12
12
 
13
- export const CodeBlock: React.FC<CodeBlockProps> = ({ block, onHover, onLeave, isHovered }) => {
13
+ export const CodeBlock: React.FC<CodeBlockProps> = ({ block, onHover, onLeave }) => {
14
14
  const [copied, setCopied] = useState(false);
15
15
  const containerRef = useRef<HTMLDivElement>(null);
16
16
  const codeRef = useRef<HTMLElement>(null);
@@ -1,6 +1,6 @@
1
1
  import React, { useRef, useEffect } from "react";
2
2
  import { isCodeFilePath } from "@plannotator/core/code-file";
3
- import { Block } from "../../types";
3
+ import type { Block } from "../../types";
4
4
  import { sanitizeBlockHtml } from "../../utils/sanitizeHtml";
5
5
  import { getImageSrc } from "../ImageThumbnail";
6
6
 
@@ -1,5 +1,5 @@
1
1
  import React, { useRef } from 'react';
2
- import { Block } from '../../types';
2
+ import type { Block } from '../../types';
3
3
  import { InlineMarkdown } from '../InlineMarkdown';
4
4
 
5
5
  interface TableBlockProps {
@@ -9,7 +9,7 @@ import {
9
9
  type ColumnDef,
10
10
  type SortingState,
11
11
  } from '@tanstack/react-table';
12
- import { Block } from '../../types';
12
+ import type { Block } from '../../types';
13
13
  import { InlineMarkdown } from '../InlineMarkdown';
14
14
  import { PopoutDialog } from '../PopoutDialog';
15
15
  import { parseTableContent, buildCsvFromRows, buildMarkdownTable } from './TableBlock';
@@ -1,4 +1,5 @@
1
- import { Slot } from "@radix-ui/react-slot";
1
+ import { mergeProps } from "@base-ui/react/merge-props";
2
+ import { useRender } from "@base-ui/react/use-render";
2
3
  import { cva, type VariantProps } from "class-variance-authority";
3
4
  import type * as React from "react";
4
5
 
@@ -6,8 +7,9 @@ import { cn } from "../../lib/utils";
6
7
 
7
8
  /**
8
9
  * Badge — small labelled token (status, category, count). Token-driven variants.
9
- * Pass `asChild` to render as a link/button. Override padding/size via className
10
- * for tighter inline badges (e.g. `px-1.5 py-px text-[10px]`).
10
+ * Pass `render` (e.g. `render={<a href=... />}`) to render as a link/button.
11
+ * Override padding/size via className for tighter inline badges
12
+ * (e.g. `px-1.5 py-px text-[10px]`).
11
13
  */
12
14
  const badgeVariants = cva(
13
15
  "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-auto whitespace-nowrap rounded-md border px-3 py-1 text-xs font-medium transition-[color,box-shadow] [&>svg]:size-3 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
@@ -31,14 +33,17 @@ const badgeVariants = cva(
31
33
  function Badge({
32
34
  className,
33
35
  variant,
34
- asChild = false,
36
+ render,
35
37
  ...props
36
- }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
37
- const Comp = asChild ? Slot : "span";
38
-
39
- return (
40
- <Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />
41
- );
38
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
39
+ return useRender({
40
+ defaultTagName: "span",
41
+ render,
42
+ props: mergeProps<"span">(
43
+ { "data-slot": "badge", className: cn(badgeVariants({ variant }), className) } as React.ComponentProps<"span">,
44
+ props,
45
+ ),
46
+ });
42
47
  }
43
48
 
44
49
  export { Badge, badgeVariants };
@@ -1,4 +1,4 @@
1
- import { Slot, Slottable } from "@radix-ui/react-slot";
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
3
  import type * as React from "react";
4
4
 
@@ -7,7 +7,7 @@ import { cn } from "../../lib/utils";
7
7
  /**
8
8
  * Button — the canonical action primitive (shared by the frontend shell and the
9
9
  * embedded plan/review apps). cva variants + sizes, optional `iconLeft`/`iconRight`
10
- * slots, and `asChild` for polymorphism.
10
+ * slots, and Base UI's `render` prop for polymorphism (e.g. `render={<a href=... />}`).
11
11
  *
12
12
  * `success` (solid, token-driven green) covers the plan app's Approve action; the
13
13
  * Feedback action uses `outline`. (No `accent` variant — `text-accent` on `bg-accent`
@@ -51,32 +51,32 @@ function Button({
51
51
  className,
52
52
  variant,
53
53
  size,
54
- asChild = false,
55
54
  iconLeft,
56
55
  iconRight,
57
56
  ...props
58
- }: React.ComponentProps<"button"> &
57
+ }: ButtonPrimitive.Props &
59
58
  VariantProps<typeof buttonVariants> & {
60
- asChild?: boolean;
61
59
  iconLeft?: ButtonIcon;
62
60
  iconRight?: ButtonIcon;
63
61
  }) {
64
- const Comp = asChild ? Slot : "button";
65
-
66
62
  return (
67
- <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props}>
63
+ <ButtonPrimitive
64
+ data-slot="button"
65
+ className={cn(buttonVariants({ variant, size, className }))}
66
+ {...props}
67
+ >
68
68
  {iconLeft ? (
69
69
  <span data-slot="button-icon" data-side="left" aria-hidden="true" className="shrink-0">
70
70
  {iconLeft}
71
71
  </span>
72
72
  ) : null}
73
- <Slottable>{children}</Slottable>
73
+ {children}
74
74
  {iconRight ? (
75
75
  <span data-slot="button-icon" data-side="right" aria-hidden="true" className="shrink-0">
76
76
  {iconRight}
77
77
  </span>
78
78
  ) : null}
79
- </Comp>
79
+ </ButtonPrimitive>
80
80
  );
81
81
  }
82
82
 
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as DialogPrimitive from "@radix-ui/react-dialog";
2
+ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
3
3
  import { X } from "lucide-react";
4
4
  import { cn } from "../../lib/utils";
5
5
 
@@ -9,31 +9,31 @@ const DialogPortal = DialogPrimitive.Portal;
9
9
  const DialogClose = DialogPrimitive.Close;
10
10
 
11
11
  const DialogOverlay = React.forwardRef<
12
- React.ComponentRef<typeof DialogPrimitive.Overlay>,
13
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
12
+ React.ComponentRef<typeof DialogPrimitive.Backdrop>,
13
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Backdrop>
14
14
  >(({ className, ...props }, ref) => (
15
- <DialogPrimitive.Overlay
15
+ <DialogPrimitive.Backdrop
16
16
  ref={ref}
17
17
  className={cn(
18
18
  "fixed inset-0 z-[110] bg-black/55 backdrop-blur-[2px]",
19
- "data-[state=open]:animate-in data-[state=open]:fade-in-0",
20
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
19
+ "transition-opacity duration-200",
20
+ "data-starting-style:opacity-0 data-ending-style:opacity-0",
21
21
  className,
22
22
  )}
23
23
  {...props}
24
24
  />
25
25
  ));
26
- DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
26
+ DialogOverlay.displayName = "DialogOverlay";
27
27
 
28
28
  const DialogContent = React.forwardRef<
29
- React.ComponentRef<typeof DialogPrimitive.Content>,
30
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
29
+ React.ComponentRef<typeof DialogPrimitive.Popup>,
30
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Popup> & {
31
31
  hideClose?: boolean;
32
32
  }
33
33
  >(({ className, children, hideClose, ...props }, ref) => (
34
34
  <DialogPortal>
35
35
  <DialogOverlay />
36
- <DialogPrimitive.Content
36
+ <DialogPrimitive.Popup
37
37
  ref={ref}
38
38
  className={cn(
39
39
  "fixed top-[50%] left-[50%] z-[110] w-full translate-x-[-50%] translate-y-[-50%]",
@@ -41,9 +41,9 @@ const DialogContent = React.forwardRef<
41
41
  "flex flex-col overflow-hidden",
42
42
  "rounded-2xl border border-border bg-popover text-popover-foreground",
43
43
  "shadow-[0_24px_80px_-36px_rgba(15,23,42,0.5)]",
44
- "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
45
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
46
- "duration-200",
44
+ "transition-[opacity,scale] duration-200",
45
+ "data-starting-style:opacity-0 data-starting-style:scale-95",
46
+ "data-ending-style:opacity-0 data-ending-style:scale-95",
47
47
  className,
48
48
  )}
49
49
  {...props}
@@ -55,10 +55,10 @@ const DialogContent = React.forwardRef<
55
55
  <span className="sr-only">Close</span>
56
56
  </DialogPrimitive.Close>
57
57
  )}
58
- </DialogPrimitive.Content>
58
+ </DialogPrimitive.Popup>
59
59
  </DialogPortal>
60
60
  ));
61
- DialogContent.displayName = DialogPrimitive.Content.displayName;
61
+ DialogContent.displayName = "DialogContent";
62
62
 
63
63
  function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
64
64
  return (
@@ -79,7 +79,7 @@ const DialogTitle = React.forwardRef<
79
79
  {...props}
80
80
  />
81
81
  ));
82
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
82
+ DialogTitle.displayName = "DialogTitle";
83
83
 
84
84
  const DialogDescription = React.forwardRef<
85
85
  React.ComponentRef<typeof DialogPrimitive.Description>,
@@ -91,7 +91,7 @@ const DialogDescription = React.forwardRef<
91
91
  {...props}
92
92
  />
93
93
  ));
94
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
94
+ DialogDescription.displayName = "DialogDescription";
95
95
 
96
96
  export {
97
97
  Dialog,
@@ -1,51 +1,58 @@
1
- import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
1
+ import { Menu as MenuPrimitive } from "@base-ui/react/menu";
2
2
  import { Check, ChevronRight, Circle } from "lucide-react";
3
3
  import type * as React from "react";
4
4
 
5
5
  import { cn } from "../../lib/utils";
6
6
 
7
7
  /**
8
- * DropdownMenu — Radix-backed menu primitive (shadcn-style). Replaces the plan app's
8
+ * DropdownMenu — Base UI Menu-backed primitive (shadcn-style). Replaces the plan app's
9
9
  * hand-rolled Approve dropdown / label picker / header menus. Icons via lucide-react.
10
10
  */
11
- function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
- return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
11
+ function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
12
+ return <MenuPrimitive.Root {...props} />;
13
13
  }
14
14
 
15
- function DropdownMenuPortal({
16
- ...props
17
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
- return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
15
+ function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
16
+ return <MenuPrimitive.Portal {...props} />;
19
17
  }
20
18
 
21
- function DropdownMenuTrigger({
22
- ...props
23
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
24
- return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
19
+ function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
20
+ return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
25
21
  }
26
22
 
23
+ const dropdownMenuPopupClassName =
24
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground origin-[var(--transform-origin)] transition-[opacity,scale] duration-150 data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95";
25
+
27
26
  function DropdownMenuContent({
28
27
  className,
28
+ side,
29
29
  sideOffset = 4,
30
+ align,
31
+ alignOffset,
30
32
  ...props
31
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
33
+ }: MenuPrimitive.Popup.Props &
34
+ Pick<MenuPrimitive.Positioner.Props, "side" | "sideOffset" | "align" | "alignOffset">) {
32
35
  return (
33
- <DropdownMenuPrimitive.Portal>
34
- <DropdownMenuPrimitive.Content
35
- data-slot="dropdown-menu-content"
36
+ <MenuPrimitive.Portal>
37
+ <MenuPrimitive.Positioner
38
+ className="isolate z-50 outline-none"
39
+ side={side}
36
40
  sideOffset={sideOffset}
37
- className={cn(
38
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=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",
39
- className,
40
- )}
41
- {...props}
42
- />
43
- </DropdownMenuPrimitive.Portal>
41
+ align={align}
42
+ alignOffset={alignOffset}
43
+ >
44
+ <MenuPrimitive.Popup
45
+ data-slot="dropdown-menu-content"
46
+ className={cn(dropdownMenuPopupClassName, "shadow-md", className)}
47
+ {...props}
48
+ />
49
+ </MenuPrimitive.Positioner>
50
+ </MenuPrimitive.Portal>
44
51
  );
45
52
  }
46
53
 
47
- function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
48
- return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
54
+ function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
55
+ return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
49
56
  }
50
57
 
51
58
  function DropdownMenuItem({
@@ -53,17 +60,17 @@ function DropdownMenuItem({
53
60
  inset,
54
61
  variant = "default",
55
62
  ...props
56
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
63
+ }: MenuPrimitive.Item.Props & {
57
64
  inset?: boolean;
58
65
  variant?: "default" | "destructive";
59
66
  }) {
60
67
  return (
61
- <DropdownMenuPrimitive.Item
68
+ <MenuPrimitive.Item
62
69
  data-slot="dropdown-menu-item"
63
70
  data-inset={inset}
64
71
  data-variant={variant}
65
72
  className={cn(
66
- "relative flex cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 font-medium outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
73
+ "relative flex cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 font-medium outline-hidden data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:data-[highlighted]:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:text-destructive dark:data-[variant=destructive]:data-[highlighted]:bg-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
67
74
  className,
68
75
  )}
69
76
  {...props}
@@ -76,54 +83,52 @@ function DropdownMenuCheckboxItem({
76
83
  children,
77
84
  checked,
78
85
  ...props
79
- }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
86
+ }: MenuPrimitive.CheckboxItem.Props) {
80
87
  return (
81
- <DropdownMenuPrimitive.CheckboxItem
88
+ <MenuPrimitive.CheckboxItem
82
89
  data-slot="dropdown-menu-checkbox-item"
83
90
  className={cn(
84
- "relative flex cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
91
+ "relative flex cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-8 pr-2 text-sm outline-hidden data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
85
92
  className,
86
93
  )}
87
94
  checked={checked}
88
95
  {...props}
89
96
  >
90
97
  <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
91
- <DropdownMenuPrimitive.ItemIndicator>
98
+ <MenuPrimitive.CheckboxItemIndicator>
92
99
  <Check className="size-4" />
93
- </DropdownMenuPrimitive.ItemIndicator>
100
+ </MenuPrimitive.CheckboxItemIndicator>
94
101
  </span>
95
102
  {children}
96
- </DropdownMenuPrimitive.CheckboxItem>
103
+ </MenuPrimitive.CheckboxItem>
97
104
  );
98
105
  }
99
106
 
100
- function DropdownMenuRadioGroup({
101
- ...props
102
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
103
- return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
107
+ function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
108
+ return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
104
109
  }
105
110
 
106
111
  function DropdownMenuRadioItem({
107
112
  className,
108
113
  children,
109
114
  ...props
110
- }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
115
+ }: MenuPrimitive.RadioItem.Props) {
111
116
  return (
112
- <DropdownMenuPrimitive.RadioItem
117
+ <MenuPrimitive.RadioItem
113
118
  data-slot="dropdown-menu-radio-item"
114
119
  className={cn(
115
- "relative flex cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
120
+ "relative flex cursor-default select-none items-center gap-2 rounded-md py-1.5 pl-8 pr-2 text-sm outline-hidden data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
116
121
  className,
117
122
  )}
118
123
  {...props}
119
124
  >
120
125
  <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
121
- <DropdownMenuPrimitive.ItemIndicator>
126
+ <MenuPrimitive.RadioItemIndicator>
122
127
  <Circle className="size-2 fill-current" />
123
- </DropdownMenuPrimitive.ItemIndicator>
128
+ </MenuPrimitive.RadioItemIndicator>
124
129
  </span>
125
130
  {children}
126
- </DropdownMenuPrimitive.RadioItem>
131
+ </MenuPrimitive.RadioItem>
127
132
  );
128
133
  }
129
134
 
@@ -131,9 +136,9 @@ function DropdownMenuLabel({
131
136
  className,
132
137
  inset,
133
138
  ...props
134
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }) {
139
+ }: MenuPrimitive.GroupLabel.Props & { inset?: boolean }) {
135
140
  return (
136
- <DropdownMenuPrimitive.Label
141
+ <MenuPrimitive.GroupLabel
137
142
  data-slot="dropdown-menu-label"
138
143
  data-inset={inset}
139
144
  className={cn("px-2 py-1.5 font-medium data-[inset]:pl-8", className)}
@@ -142,12 +147,9 @@ function DropdownMenuLabel({
142
147
  );
143
148
  }
144
149
 
145
- function DropdownMenuSeparator({
146
- className,
147
- ...props
148
- }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
150
+ function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
149
151
  return (
150
- <DropdownMenuPrimitive.Separator
152
+ <MenuPrimitive.Separator
151
153
  data-slot="dropdown-menu-separator"
152
154
  className={cn("-mx-1 my-1 h-px bg-border", className)}
153
155
  {...props}
@@ -179,8 +181,8 @@ function DropdownMenuShortcut({
179
181
  );
180
182
  }
181
183
 
182
- function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
183
- return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
184
+ function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
185
+ return <MenuPrimitive.SubmenuRoot {...props} />;
184
186
  }
185
187
 
186
188
  function DropdownMenuSubTrigger({
@@ -188,36 +190,48 @@ function DropdownMenuSubTrigger({
188
190
  inset,
189
191
  children,
190
192
  ...props
191
- }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {
193
+ }: MenuPrimitive.SubmenuTrigger.Props & { inset?: boolean }) {
192
194
  return (
193
- <DropdownMenuPrimitive.SubTrigger
195
+ <MenuPrimitive.SubmenuTrigger
194
196
  data-slot="dropdown-menu-sub-trigger"
195
197
  data-inset={inset}
196
198
  className={cn(
197
- "flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
199
+ "flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-hidden data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground",
198
200
  className,
199
201
  )}
200
202
  {...props}
201
203
  >
202
204
  {children}
203
205
  <ChevronRight className="ml-auto size-4" />
204
- </DropdownMenuPrimitive.SubTrigger>
206
+ </MenuPrimitive.SubmenuTrigger>
205
207
  );
206
208
  }
207
209
 
208
210
  function DropdownMenuSubContent({
209
211
  className,
212
+ side = "right",
213
+ sideOffset = 0,
214
+ align = "start",
215
+ alignOffset = -3,
210
216
  ...props
211
- }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
217
+ }: MenuPrimitive.Popup.Props &
218
+ Pick<MenuPrimitive.Positioner.Props, "side" | "sideOffset" | "align" | "alignOffset">) {
212
219
  return (
213
- <DropdownMenuPrimitive.SubContent
214
- data-slot="dropdown-menu-sub-content"
215
- className={cn(
216
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=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",
217
- className,
218
- )}
219
- {...props}
220
- />
220
+ <MenuPrimitive.Portal>
221
+ <MenuPrimitive.Positioner
222
+ className="isolate z-50 outline-none"
223
+ side={side}
224
+ sideOffset={sideOffset}
225
+ align={align}
226
+ alignOffset={alignOffset}
227
+ >
228
+ <MenuPrimitive.Popup
229
+ data-slot="dropdown-menu-sub-content"
230
+ className={cn(dropdownMenuPopupClassName, "shadow-lg", className)}
231
+ {...props}
232
+ />
233
+ </MenuPrimitive.Positioner>
234
+ </MenuPrimitive.Portal>
221
235
  );
222
236
  }
223
237