@plannotator/ui 0.53.1 → 0.55.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/HANDOFF.md +274 -14
- package/README.md +114 -11
- package/components/AgentToolAnnouncementDialog.tsx +1 -1
- package/components/AgentsTab.tsx +1 -1
- package/components/AnnotationPanel.tsx +142 -24
- package/components/AnnotationSidebar.tsx +1 -1
- package/components/AnnotationToolbar.tsx +64 -9
- package/components/ApproveDropdown.tsx +7 -9
- package/components/AskSessionAnnouncementDialog.tsx +1 -1
- package/components/AttachmentsButton.tsx +1 -1
- package/components/BlockRenderer.tsx +22 -5
- package/components/CodeFilePopout.tsx +139 -4
- package/components/CodeSnippetPreview.tsx +174 -0
- package/components/CommentPopover.tsx +44 -8
- package/components/DecisionControl.tsx +31 -5
- package/components/DeferredMarkdownContentsRail.tsx +38 -0
- package/components/DiagramBlock.tsx +17 -12
- package/components/DiffFileTree.tsx +7 -6
- package/components/DocumentContents.tsx +74 -0
- package/components/EditorAnnotationCard.tsx +1 -1
- package/components/ExportModal.tsx +77 -7
- package/components/HostTitle.tsx +0 -4
- package/components/ImageAnnotator/Toolbar.tsx +25 -1
- package/components/ImageAnnotator/index.tsx +24 -4
- package/components/ImageLightbox.tsx +722 -126
- package/components/ImagePeek.tsx +38 -4
- package/components/ImageRegions.tsx +369 -0
- package/components/ImageThumbnail.tsx +1 -1
- package/components/ImageZoomStage.tsx +796 -0
- package/components/InboxAnnouncementDialog.tsx +202 -0
- package/components/InlineMarkdown.tsx +167 -174
- package/components/KeyboardShortcuts.tsx +23 -0
- package/components/LazyBlockBoundary.tsx +100 -0
- package/components/ListMarker.tsx +3 -3
- package/components/LookAndFeelAnnouncementDialog.tsx +12 -2
- package/components/MarkdownContentsRail.tsx +777 -0
- package/components/MenuVersionSection.tsx +31 -9
- package/components/PlanDecisionMenuExtras.tsx +129 -0
- package/components/PlanHeaderMenu.tsx +15 -1
- package/components/ProviderIcons.tsx +8 -0
- package/components/RefHoverCard.tsx +913 -0
- package/components/SendFailureToast.tsx +126 -0
- package/components/Settings.tsx +617 -126
- package/components/TableOfContents.tsx +20 -6
- package/components/TerminalToolsAnnouncementDialog.tsx +1 -1
- package/components/ThemeProvider.tsx +14 -1
- package/components/ThemeTab.tsx +16 -24
- package/components/Tooltip.tsx +49 -18
- package/components/Viewer.tsx +288 -43
- package/components/ai/DocumentAIChatPanel.tsx +29 -3
- package/components/ai/NewChatButton.tsx +74 -0
- package/components/blocks/CodeBlock.tsx +1 -1
- package/components/blocks/HtmlBlock.tsx +7 -1
- package/components/blocks/QuestionBlock.tsx +532 -62
- package/components/diagram/DiagramComposer.tsx +5 -1
- package/components/diagram/DiagramPending.tsx +12 -2
- package/components/diagram/DiagramViewer.tsx +19 -3
- package/components/html-embed/HtmlEmbedBlock.tsx +374 -0
- package/components/html-embed/SvgFenceBlock.tsx +46 -0
- package/components/html-embed/index.ts +3 -0
- package/components/html-embed/languages.ts +15 -0
- package/components/html-embed/loadGate.ts +107 -0
- package/components/html-viewer/HtmlViewer.tsx +82 -6
- package/components/html-viewer/bridge-script.asset.js +173 -1
- package/components/html-viewer/bridge-script.ts +173 -1
- package/components/html-viewer/useHtmlAnnotation.ts +125 -0
- package/components/imageZoom.ts +276 -0
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -1
- package/components/plan-diff/PlanDiffViewer.tsx +13 -2
- package/components/plan-diff/PlanRawDiffView.tsx +1 -1
- package/components/sidebar/FileBrowser.tsx +16 -2
- package/components/sidebar/SidebarTabs.tsx +3 -0
- package/config/index.ts +3 -0
- package/config/reviewView.ts +41 -1
- package/config/settings.ts +155 -4
- package/configure.ts +28 -0
- package/contents-rail.css +337 -0
- package/hooks/pfm/useCodeFilePopout.ts +7 -1
- package/hooks/useAIChat.ts +72 -25
- package/hooks/useAnnotationDraft.ts +71 -6
- package/hooks/useAnnotationHighlighter.ts +57 -3
- package/hooks/useCodeAnnotationDraft.ts +113 -17
- package/hooks/useCookieHostSetting.ts +96 -0
- package/hooks/useDocumentFont.ts +17 -0
- package/hooks/useDocumentTextSize.ts +20 -0
- package/hooks/useFileBrowser.ts +10 -1
- package/hooks/useHeadingScrollSpy.ts +77 -0
- package/hooks/useHoverPreview.ts +94 -0
- package/hooks/useLinkedDoc.ts +226 -13
- package/hooks/usePackageChangesSetting.ts +90 -0
- package/hooks/usePlanLookDialog.ts +67 -0
- package/hooks/usePrefersReducedMotion.ts +23 -0
- package/hooks/useSharing.ts +11 -7
- package/hooks/useSwipeStep.ts +597 -0
- package/hooks/useThemePaletteHalves.ts +37 -0
- package/hooks/useUpdateCheck.ts +22 -4
- package/hooks/useUpdateToast.ts +1 -1
- package/package.json +26 -23
- package/shortcuts/code-review/annotationToolbar.shortcuts.ts +1 -0
- package/shortcuts/code-review/explorer.shortcuts.ts +82 -0
- package/shortcuts/core.ts +63 -4
- package/shortcuts/index.ts +1 -0
- package/shortcuts/plan-review/annotationPanel.shortcuts.ts +1 -1
- package/shortcuts/plan-review/commentPopover.shortcuts.ts +1 -0
- package/shortcuts/runtime.ts +4 -3
- package/styles.css +1 -1
- package/theme.css +179 -1
- package/themes/plannotator-graphite.css +66 -0
- package/types.ts +26 -9
- package/utils/agentSwitch.ts +41 -20
- package/utils/agentToolSetting.ts +1 -1
- package/utils/aiChatFormat.ts +23 -7
- package/utils/annotationHelpers.ts +32 -4
- package/utils/annotationOwnsHighlight.ts +4 -2
- package/utils/asciidocParser.ts +570 -0
- package/utils/autoUpdateNotice.ts +24 -0
- package/utils/belowTitleSlot.ts +174 -0
- package/utils/blockParse.ts +45 -0
- package/utils/codeLineScroll.ts +100 -0
- package/utils/commentSubmitKey.ts +48 -0
- package/utils/cssFontFamily.ts +65 -0
- package/utils/decisionSpec.ts +190 -2
- package/utils/defaultNotesApp.ts +1 -1
- package/utils/diffFonts.ts +4 -1
- package/utils/documentFont.ts +68 -0
- package/utils/documentTextSize.ts +54 -0
- package/utils/fontDetect.ts +63 -0
- package/utils/headingScrollSpy.ts +92 -0
- package/utils/inboxAnnouncement.ts +78 -0
- package/utils/lookAndFeelAnnouncement.ts +82 -5
- package/utils/markdownExtensions.ts +2 -2
- package/utils/mermaid.ts +20 -3
- package/utils/mermaidTheme.ts +3 -51
- package/utils/notion.ts +58 -0
- package/utils/parser.ts +28 -1
- package/utils/platform.ts +46 -0
- package/utils/refCard.ts +82 -0
- package/utils/richClipboard.ts +380 -0
- package/utils/richClipboardLists.ts +57 -0
- package/utils/richCopyPolicy.ts +22 -0
- package/utils/scrollAnchor.ts +82 -0
- package/utils/sectionRefs.ts +123 -0
- package/utils/selectionBoundary.ts +40 -0
- package/utils/sendFailure.ts +85 -0
- package/utils/sharing.ts +5 -2
- package/utils/themeRegistry.ts +10 -0
- package/utils/themeTokens.ts +74 -0
- package/utils/updateNotice.ts +30 -8
- package/utils/vscodePanel.ts +9 -0
|
@@ -22,6 +22,17 @@ interface TableOfContentsProps {
|
|
|
22
22
|
linkedDocFilepath?: string | null;
|
|
23
23
|
onLinkedDocBack?: () => void;
|
|
24
24
|
backLabel?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The element a click scrolls. Default: the `ScrollViewportProvider`
|
|
27
|
+
* context's viewport (plan review). A host that draws the list outside the
|
|
28
|
+
* document's scroll area (the Inbox's comments column) names it here.
|
|
29
|
+
*/
|
|
30
|
+
scrollViewport?: HTMLElement | null;
|
|
31
|
+
/**
|
|
32
|
+
* The deepest heading level listed. Default 3 (plan review's list); the
|
|
33
|
+
* contents rail's surface (`DocumentContents`) lists 1 to 4, as the rail does.
|
|
34
|
+
*/
|
|
35
|
+
maxLevel?: number;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
// The prototype's TOC is a FLAT list — heading depth is conveyed by indentation
|
|
@@ -43,7 +54,7 @@ function flattenToc(items: TocItem[]): TocItem[] {
|
|
|
43
54
|
// H1 flush + near-full strength, H2/H3 indented and dimmed to muted. Active row
|
|
44
55
|
// is a soft neutral surface tint (not a loud primary fill).
|
|
45
56
|
function itemClasses(level: number, isActive: boolean): string {
|
|
46
|
-
const indent = level <= 1 ? '' : level === 2 ? 'ml-3' : 'ml-6';
|
|
57
|
+
const indent = level <= 1 ? '' : level === 2 ? 'ml-3' : level === 3 ? 'ml-6' : 'ml-9';
|
|
47
58
|
const tone = isActive
|
|
48
59
|
? 'bg-surface-1 text-foreground'
|
|
49
60
|
: level <= 1
|
|
@@ -62,6 +73,8 @@ export function TableOfContents({
|
|
|
62
73
|
linkedDocFilepath,
|
|
63
74
|
onLinkedDocBack,
|
|
64
75
|
backLabel,
|
|
76
|
+
scrollViewport: scrollViewportProp,
|
|
77
|
+
maxLevel = 3,
|
|
65
78
|
}: TableOfContentsProps) {
|
|
66
79
|
// Annotation count per section (kept — production feature).
|
|
67
80
|
const annotationCounts = useMemo(
|
|
@@ -69,15 +82,16 @@ export function TableOfContents({
|
|
|
69
82
|
[blocks, annotations]
|
|
70
83
|
);
|
|
71
84
|
|
|
72
|
-
// Build the hierarchy (filters to heading levels ≤
|
|
73
|
-
// then flatten to a plain list.
|
|
85
|
+
// Build the hierarchy (filters to heading levels ≤ maxLevel and attaches
|
|
86
|
+
// counts), then flatten to a plain list.
|
|
74
87
|
const tocItems = useMemo(
|
|
75
|
-
() => flattenToc(buildTocHierarchy(blocks, annotationCounts)),
|
|
76
|
-
[blocks, annotationCounts]
|
|
88
|
+
() => flattenToc(buildTocHierarchy(blocks, annotationCounts, maxLevel)),
|
|
89
|
+
[blocks, annotationCounts, maxLevel]
|
|
77
90
|
);
|
|
78
91
|
|
|
79
92
|
// The real scroll element is the OverlayScrollArea viewport, not <main>.
|
|
80
|
-
const
|
|
93
|
+
const contextViewport = useScrollViewport();
|
|
94
|
+
const scrollViewport = scrollViewportProp === undefined ? contextViewport : scrollViewportProp;
|
|
81
95
|
|
|
82
96
|
// Smooth scroll-to-heading, accounting for the sticky header.
|
|
83
97
|
const handleNavigate = useCallback(
|
|
@@ -231,7 +231,7 @@ function DemoPlayer({ demo, reducedMotion }: DemoPlayerProps) {
|
|
|
231
231
|
data-terminal-tools-playback={playing ? 'pause' : 'play'}
|
|
232
232
|
className={
|
|
233
233
|
playing
|
|
234
|
-
? 'absolute bottom-3 left-3 grid size-9 place-items-center rounded-full border border-border/60 bg-background/70 text-foreground opacity-0 outline-none backdrop-blur-sm transition-opacity motion-reduce:transition-none group-hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-primary'
|
|
234
|
+
? 'absolute bottom-3 left-3 grid size-9 place-items-center rounded-full border border-border/60 bg-background/70 text-foreground opacity-0 outline-none backdrop-blur-sm transition-opacity motion-reduce:transition-none group-hover:opacity-100 focus-visible:opacity-100 [@media(hover:none)]:opacity-100 focus-visible:ring-2 focus-visible:ring-primary'
|
|
235
235
|
: 'absolute left-1/2 top-1/2 grid size-16 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full border border-border/60 bg-background/80 text-foreground shadow-lg outline-none backdrop-blur-sm transition-transform motion-reduce:transition-none hover:scale-105 focus-visible:ring-2 focus-visible:ring-primary'
|
|
236
236
|
}
|
|
237
237
|
>
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from '../utils/themeRegistry';
|
|
19
19
|
import type { Mode } from './themeModes';
|
|
20
20
|
import { usePrintMedia } from '../hooks/usePrintMedia';
|
|
21
|
+
import { markRootPlatform } from '../utils/platform';
|
|
21
22
|
|
|
22
23
|
// Kept here because published consumers already import Mode from ThemeProvider.
|
|
23
24
|
export type { Mode } from './themeModes';
|
|
@@ -62,12 +63,22 @@ const ThemeProviderContext = createContext<ThemeProviderState>({
|
|
|
62
63
|
manageFavicon: false,
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Sync theme classes on <html> without stripping non-theme classes (e.g. transitions-ready).
|
|
68
|
+
*
|
|
69
|
+
* A page that sets an inline `color-scheme` on <html> before its scripts run
|
|
70
|
+
* (Plannotator's packed pages do, so the empty page does not paint white under
|
|
71
|
+
* a dark theme) hands it over here: from the first render it follows the mode
|
|
72
|
+
* on screen, so it never goes stale after a theme switch. A host page that sets
|
|
73
|
+
* none keeps none.
|
|
74
|
+
*/
|
|
66
75
|
function applyThemeClasses(themeId: string, resolvedMode: 'dark' | 'light'): void {
|
|
67
76
|
const el = document.documentElement;
|
|
68
77
|
const themeClass = `theme-${themeId}`;
|
|
69
78
|
const wantLight = resolvedMode === 'light';
|
|
70
79
|
|
|
80
|
+
if (el.style.colorScheme && el.style.colorScheme !== resolvedMode) el.style.colorScheme = resolvedMode;
|
|
81
|
+
|
|
71
82
|
if (el.classList.contains(themeClass) && el.classList.contains('light') === wantLight) return;
|
|
72
83
|
|
|
73
84
|
for (const cls of Array.from(el.classList)) {
|
|
@@ -223,6 +234,8 @@ export function ThemeProvider({
|
|
|
223
234
|
// without this the first frame has no valid --background/--foreground.
|
|
224
235
|
if (typeof window !== 'undefined') {
|
|
225
236
|
applyThemeClasses(colorTheme, resolvedMode);
|
|
237
|
+
// theme.css scopes the Linux-only font-feature rule to this class (#1961).
|
|
238
|
+
markRootPlatform();
|
|
226
239
|
}
|
|
227
240
|
|
|
228
241
|
// Keep class in sync after state changes
|
package/components/ThemeTab.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { useTheme } from './ThemeProvider';
|
|
3
3
|
import { THEME_MODES } from './themeModes';
|
|
4
|
-
import {
|
|
4
|
+
import type { ThemeHalf, ThemeInfo } from '../utils/themeRegistry';
|
|
5
|
+
import { useThemePaletteHalves } from '../hooks/useThemePaletteHalves';
|
|
5
6
|
import { configStore } from '../config/configStore';
|
|
6
7
|
import { useConfigValue } from '../config/useConfig';
|
|
7
8
|
import { faviconDataUrl, type FaviconStyle } from '@plannotator/core/favicon';
|
|
@@ -56,27 +57,19 @@ const FaviconStyleControl: React.FC<{ selected: FaviconStyle }> = ({ selected })
|
|
|
56
57
|
</div>
|
|
57
58
|
);
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
mode,
|
|
62
|
-
setMode,
|
|
63
|
-
lightTheme,
|
|
64
|
-
darkTheme,
|
|
65
|
-
setHalfTheme,
|
|
66
|
-
availableThemes,
|
|
67
|
-
preferredMode,
|
|
68
|
-
manageFavicon,
|
|
69
|
-
} = useTheme();
|
|
70
|
-
const faviconStyle = useConfigValue('faviconStyle');
|
|
60
|
+
// The picker's state lives in hooks/useThemePaletteHalves; re-exported here so existing imports keep working.
|
|
61
|
+
export { useThemePaletteHalves, type ThemePaletteHalves } from '../hooks/useThemePaletteHalves';
|
|
71
62
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const [half
|
|
75
|
-
|
|
63
|
+
/** The five colors a palette tile shows for one half, in ThemeTab's order. */
|
|
64
|
+
export function paletteSwatchColors(theme: ThemeInfo, half: ThemeHalf): string[] {
|
|
65
|
+
const colors = theme.colors[half];
|
|
66
|
+
return [colors.primary, colors.secondary, colors.accent, colors.background, colors.foreground];
|
|
67
|
+
}
|
|
76
68
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
69
|
+
export const ThemeTab: React.FC<ThemeTabProps> = ({ onPreview, compact }) => {
|
|
70
|
+
const { mode, setMode, manageFavicon } = useTheme();
|
|
71
|
+
const faviconStyle = useConfigValue('faviconStyle');
|
|
72
|
+
const { half, setHalf, pair, themes, nameOf, choose } = useThemePaletteHalves();
|
|
80
73
|
|
|
81
74
|
const summary = (
|
|
82
75
|
<div className={`flex items-center gap-1.5 text-[11px] text-muted-foreground ${compact ? '' : 'flex-wrap'}`}>
|
|
@@ -190,11 +183,10 @@ export const ThemeTab: React.FC<ThemeTabProps> = ({ onPreview, compact }) => {
|
|
|
190
183
|
<div className={`grid gap-2 overflow-y-auto pr-1 ${compact ? 'grid-cols-4' : 'grid-cols-3'}`}>
|
|
191
184
|
{themes.map(theme => {
|
|
192
185
|
const isSelected = pair[half] === theme.id;
|
|
193
|
-
const colors = theme.colors[half];
|
|
194
186
|
return (
|
|
195
187
|
<button
|
|
196
188
|
key={theme.id}
|
|
197
|
-
onClick={() =>
|
|
189
|
+
onClick={() => choose(half, theme.id)}
|
|
198
190
|
className={`relative p-2 rounded-md border text-left transition-colors ${
|
|
199
191
|
isSelected
|
|
200
192
|
? 'border-primary bg-primary/5'
|
|
@@ -209,7 +201,7 @@ export const ThemeTab: React.FC<ThemeTabProps> = ({ onPreview, compact }) => {
|
|
|
209
201
|
)}
|
|
210
202
|
{/* Color swatches */}
|
|
211
203
|
<div className="flex gap-1 mb-1.5">
|
|
212
|
-
{
|
|
204
|
+
{paletteSwatchColors(theme, half).map((color, i) => (
|
|
213
205
|
<div
|
|
214
206
|
key={i}
|
|
215
207
|
className="w-3 h-3 rounded-full border border-border/50"
|
package/components/Tooltip.tsx
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* One tooltip timing for every surface (owner's design-system ruling q4): open
|
|
6
|
+
* after 150 ms of rest, and open the next one at once for 400 ms after one
|
|
7
|
+
* closes. A host's own `TooltipProvider` props, or a trigger's
|
|
8
|
+
* `delayDuration`, still win.
|
|
9
|
+
*/
|
|
10
|
+
export const TOOLTIP_DELAY_MS = 150;
|
|
11
|
+
export const TOOLTIP_SKIP_DELAY_MS = 400;
|
|
12
|
+
|
|
13
|
+
interface TooltipProviderContextValue {
|
|
14
|
+
disableHoverablePopup: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Null outside a `TooltipProvider`: a lone Tooltip then sets its own 150 ms (Base UI's own default is 600). */
|
|
18
|
+
const TooltipProviderContext = React.createContext<TooltipProviderContextValue | null>(null);
|
|
19
|
+
|
|
4
20
|
/**
|
|
5
21
|
* TooltipProvider keeps the Radix-era prop names (`delayDuration`,
|
|
6
22
|
* `skipDelayDuration`, `disableHoverableContent`) as its public API and maps
|
|
@@ -9,8 +25,6 @@ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
|
|
|
9
25
|
* in Base UI (it moved to per-root `disableHoverablePopup`), so it is carried
|
|
10
26
|
* via context and applied to every Tooltip root under the provider.
|
|
11
27
|
*/
|
|
12
|
-
const DisableHoverablePopupContext = React.createContext(false);
|
|
13
|
-
|
|
14
28
|
interface TooltipProviderProps {
|
|
15
29
|
delayDuration?: number;
|
|
16
30
|
skipDelayDuration?: number;
|
|
@@ -23,13 +37,15 @@ export const TooltipProvider: React.FC<TooltipProviderProps> = ({
|
|
|
23
37
|
skipDelayDuration,
|
|
24
38
|
disableHoverableContent = false,
|
|
25
39
|
children,
|
|
26
|
-
}) =>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
}) => {
|
|
41
|
+
// One object per setting, so tooltips behind a memo boundary don't re-render with the provider.
|
|
42
|
+
const value = React.useMemo(() => ({ disableHoverablePopup: disableHoverableContent }), [disableHoverableContent]);
|
|
43
|
+
return (
|
|
44
|
+
<BaseTooltip.Provider delay={delayDuration ?? TOOLTIP_DELAY_MS} timeout={skipDelayDuration ?? TOOLTIP_SKIP_DELAY_MS}>
|
|
45
|
+
<TooltipProviderContext.Provider value={value}>{children}</TooltipProviderContext.Provider>
|
|
46
|
+
</BaseTooltip.Provider>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
33
49
|
|
|
34
50
|
interface TooltipProps {
|
|
35
51
|
content: React.ReactNode;
|
|
@@ -63,6 +79,13 @@ interface TooltipProps {
|
|
|
63
79
|
* default, where the Escape that closes a tooltip goes no further.
|
|
64
80
|
*/
|
|
65
81
|
escapePassesThrough?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* When true, the tooltip is shown whatever the pointer does: a host shows
|
|
84
|
+
* it while the element that holds the trigger has keyboard focus (the
|
|
85
|
+
* Inbox's row badges). False or unset: hover and focus on the trigger decide,
|
|
86
|
+
* as before.
|
|
87
|
+
*/
|
|
88
|
+
forceOpen?: boolean;
|
|
66
89
|
}
|
|
67
90
|
|
|
68
91
|
export const Tooltip: React.FC<TooltipProps> = ({
|
|
@@ -76,25 +99,30 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
|
|
76
99
|
shouldOpen,
|
|
77
100
|
container,
|
|
78
101
|
escapePassesThrough = false,
|
|
102
|
+
forceOpen,
|
|
79
103
|
}) => {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
//
|
|
104
|
+
const provider = React.useContext(TooltipProviderContext);
|
|
105
|
+
const disableHoverablePopup = provider?.disableHoverablePopup ?? false;
|
|
106
|
+
// Under a provider, an unset delay takes the provider's; alone, the shared default.
|
|
107
|
+
const triggerDelay = delayDuration ?? (provider ? undefined : TOOLTIP_DELAY_MS);
|
|
108
|
+
// Controlled for escapePassesThrough (the Escape close is canceled, so Base UI neither prevents nor stops the
|
|
109
|
+
// key, and the tooltip is closed here instead) and for forceOpen (shown on top of what hover decides).
|
|
83
110
|
const [controlledOpen, setControlledOpen] = React.useState(false);
|
|
84
|
-
const
|
|
111
|
+
const controlledMode = escapePassesThrough || forceOpen !== undefined;
|
|
112
|
+
const controlled = controlledMode ? { open: controlledOpen || forceOpen === true } : {};
|
|
85
113
|
return (
|
|
86
114
|
<BaseTooltip.Root
|
|
87
115
|
disableHoverablePopup={disableHoverablePopup}
|
|
88
116
|
{...controlled}
|
|
89
117
|
onOpenChange={
|
|
90
|
-
shouldOpen ||
|
|
118
|
+
shouldOpen || controlledMode
|
|
91
119
|
? (open, details) => {
|
|
92
120
|
if (open && shouldOpen && !(details.trigger && shouldOpen(details.trigger))) {
|
|
93
121
|
details.cancel();
|
|
94
122
|
return;
|
|
95
123
|
}
|
|
96
|
-
if (!
|
|
97
|
-
if (!open && details.reason === 'escape-key') {
|
|
124
|
+
if (!controlledMode) return;
|
|
125
|
+
if (escapePassesThrough && !open && details.reason === 'escape-key') {
|
|
98
126
|
details.cancel();
|
|
99
127
|
details.allowPropagation();
|
|
100
128
|
}
|
|
@@ -103,13 +131,16 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
|
|
103
131
|
: undefined
|
|
104
132
|
}
|
|
105
133
|
>
|
|
106
|
-
<BaseTooltip.Trigger render={children} delay={
|
|
134
|
+
<BaseTooltip.Trigger render={children} delay={triggerDelay} />
|
|
107
135
|
<BaseTooltip.Portal container={container ?? undefined}>
|
|
108
136
|
{/* Above the annotation toolbar (z-[100]), so a tooltip that flips toward it is never tucked under it. */}
|
|
109
137
|
<BaseTooltip.Positioner side={side} align={align} sideOffset={sideOffset} className="isolate z-[110]">
|
|
138
|
+
{/* A borderless float (owner's design-system ruling q4): the shadow (--float-shadow, theme.css) separates
|
|
139
|
+
it from the page. The 1px border stays, transparent, so forced colors (Windows High Contrast), which
|
|
140
|
+
drop shadows and paint borders in the system colour, still draw its edge. */}
|
|
110
141
|
<BaseTooltip.Popup
|
|
111
142
|
data-pn-tooltip=""
|
|
112
|
-
className={`z-[110] px-2 py-1 text-xs bg-popover text-popover-foreground border border-
|
|
143
|
+
className={`z-[110] px-2 py-1 text-xs bg-popover text-popover-foreground border border-transparent rounded shadow-[var(--float-shadow)] origin-[var(--transform-origin)] transition-[opacity,scale] duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 ${
|
|
113
144
|
wide
|
|
114
145
|
? 'max-w-[min(260px,calc(100vw-1rem))] leading-snug whitespace-normal [overflow-wrap:anywhere]'
|
|
115
146
|
: 'whitespace-nowrap'
|