@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.
- package/components/AnnotationPanel.tsx +68 -37
- package/components/AnnotationToolbar.tsx +1 -1
- package/components/BlockRenderer.tsx +1 -1
- package/components/EditorAnnotationCard.tsx +13 -10
- package/components/ImageAnnotator/index.tsx +1 -1
- package/components/OpenInAppButton.tsx +25 -29
- package/components/PopoutDialog.tsx +46 -32
- package/components/Popover.tsx +18 -12
- package/components/SearchableSelect.tsx +4 -7
- package/components/Tooltip.tsx +50 -20
- package/components/Viewer.tsx +23 -9
- package/components/blocks/CodeBlock.tsx +2 -2
- package/components/blocks/HtmlBlock.tsx +1 -1
- package/components/blocks/TableBlock.tsx +1 -1
- package/components/blocks/TablePopout.tsx +1 -1
- package/components/ui/badge.tsx +15 -10
- package/components/ui/button.tsx +10 -10
- package/components/ui/dialog.tsx +17 -17
- package/components/ui/dropdown-menu.tsx +80 -66
- package/components/ui/tabs.tsx +11 -11
- package/hooks/useAnnotationHighlighter.ts +44 -6
- package/hooks/useCodeAnnotationDraft.ts +0 -5
- package/hooks/usePinpoint.ts +2 -2
- package/package.json +5 -12
- package/styles.css +1 -1
- package/theme.css +17 -0
- package/utils/parser.ts +1 -1
- package/utils/sharing.ts +1 -1
package/components/Viewer.tsx
CHANGED
|
@@ -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,
|
|
5
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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';
|
package/components/ui/badge.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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 `
|
|
10
|
-
* for tighter inline badges
|
|
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
|
-
|
|
36
|
+
render,
|
|
35
37
|
...props
|
|
36
|
-
}:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
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 };
|
package/components/ui/button.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 `
|
|
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
|
-
}:
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
</
|
|
79
|
+
</ButtonPrimitive>
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
|
package/components/ui/dialog.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
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.
|
|
13
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.
|
|
12
|
+
React.ComponentRef<typeof DialogPrimitive.Backdrop>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Backdrop>
|
|
14
14
|
>(({ className, ...props }, ref) => (
|
|
15
|
-
<DialogPrimitive.
|
|
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
|
-
"
|
|
20
|
-
"data-
|
|
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 =
|
|
26
|
+
DialogOverlay.displayName = "DialogOverlay";
|
|
27
27
|
|
|
28
28
|
const DialogContent = React.forwardRef<
|
|
29
|
-
React.ComponentRef<typeof DialogPrimitive.
|
|
30
|
-
React.ComponentPropsWithoutRef<typeof DialogPrimitive.
|
|
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.
|
|
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
|
-
"
|
|
45
|
-
"data-
|
|
46
|
-
"
|
|
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.
|
|
58
|
+
</DialogPrimitive.Popup>
|
|
59
59
|
</DialogPortal>
|
|
60
60
|
));
|
|
61
|
-
DialogContent.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 =
|
|
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 =
|
|
94
|
+
DialogDescription.displayName = "DialogDescription";
|
|
95
95
|
|
|
96
96
|
export {
|
|
97
97
|
Dialog,
|
|
@@ -1,51 +1,58 @@
|
|
|
1
|
-
import
|
|
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 —
|
|
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 }:
|
|
12
|
-
return <
|
|
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
|
-
}:
|
|
33
|
+
}: MenuPrimitive.Popup.Props &
|
|
34
|
+
Pick<MenuPrimitive.Positioner.Props, "side" | "sideOffset" | "align" | "alignOffset">) {
|
|
32
35
|
return (
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
+
<MenuPrimitive.Portal>
|
|
37
|
+
<MenuPrimitive.Positioner
|
|
38
|
+
className="isolate z-50 outline-none"
|
|
39
|
+
side={side}
|
|
36
40
|
sideOffset={sideOffset}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 }:
|
|
48
|
-
return <
|
|
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
|
-
}:
|
|
63
|
+
}: MenuPrimitive.Item.Props & {
|
|
57
64
|
inset?: boolean;
|
|
58
65
|
variant?: "default" | "destructive";
|
|
59
66
|
}) {
|
|
60
67
|
return (
|
|
61
|
-
<
|
|
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
|
|
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
|
-
}:
|
|
86
|
+
}: MenuPrimitive.CheckboxItem.Props) {
|
|
80
87
|
return (
|
|
81
|
-
<
|
|
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
|
|
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
|
-
<
|
|
98
|
+
<MenuPrimitive.CheckboxItemIndicator>
|
|
92
99
|
<Check className="size-4" />
|
|
93
|
-
</
|
|
100
|
+
</MenuPrimitive.CheckboxItemIndicator>
|
|
94
101
|
</span>
|
|
95
102
|
{children}
|
|
96
|
-
</
|
|
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
|
-
}:
|
|
115
|
+
}: MenuPrimitive.RadioItem.Props) {
|
|
111
116
|
return (
|
|
112
|
-
<
|
|
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
|
|
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
|
-
<
|
|
126
|
+
<MenuPrimitive.RadioItemIndicator>
|
|
122
127
|
<Circle className="size-2 fill-current" />
|
|
123
|
-
</
|
|
128
|
+
</MenuPrimitive.RadioItemIndicator>
|
|
124
129
|
</span>
|
|
125
130
|
{children}
|
|
126
|
-
</
|
|
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
|
-
}:
|
|
139
|
+
}: MenuPrimitive.GroupLabel.Props & { inset?: boolean }) {
|
|
135
140
|
return (
|
|
136
|
-
<
|
|
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
|
-
<
|
|
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 }:
|
|
183
|
-
return <
|
|
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
|
-
}:
|
|
193
|
+
}: MenuPrimitive.SubmenuTrigger.Props & { inset?: boolean }) {
|
|
192
194
|
return (
|
|
193
|
-
<
|
|
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
|
|
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
|
-
</
|
|
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
|
-
}:
|
|
217
|
+
}: MenuPrimitive.Popup.Props &
|
|
218
|
+
Pick<MenuPrimitive.Positioner.Props, "side" | "sideOffset" | "align" | "alignOffset">) {
|
|
212
219
|
return (
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|