-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Unify dialog glass and fix composer overlays #4365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ import { serializeComposerFileLink } from "@t3tools/shared/composerTrigger"; | |
| import { createModelSelection, normalizeModelSlug } from "@t3tools/shared/model"; | ||
| import { | ||
| memo, | ||
| type ReactNode, | ||
| useCallback, | ||
| useEffect, | ||
| useImperativeHandle, | ||
|
|
@@ -34,6 +35,7 @@ import { | |
| useRef, | ||
| useState, | ||
| } from "react"; | ||
| import { createPortal } from "react-dom"; | ||
| import { | ||
| clampCollapsedComposerCursor, | ||
| type ComposerTrigger, | ||
|
|
@@ -93,6 +95,64 @@ import { buildExpandedImagePreview, type ExpandedImagePreview } from "./Expanded | |
| import { basenameOfPath } from "../../pierre-icons"; | ||
| import { cn, randomUUID } from "~/lib/utils"; | ||
| import { Separator } from "../ui/separator"; | ||
|
|
||
| function ComposerCommandMenuLayer(props: { anchor: HTMLElement | null; children: ReactNode }) { | ||
| const [position, setPosition] = useState<{ | ||
| bottom: number; | ||
| left: number; | ||
| maxHeight: number; | ||
| width: number; | ||
| } | null>(null); | ||
|
|
||
| useLayoutEffect(() => { | ||
| const anchor = props.anchor; | ||
| if (!anchor) { | ||
| setPosition(null); | ||
| return; | ||
| } | ||
|
|
||
| const updatePosition = () => { | ||
| const rect = anchor.getBoundingClientRect(); | ||
| setPosition({ | ||
| bottom: window.innerHeight - rect.top + 8, | ||
| left: rect.left, | ||
| maxHeight: Math.max(96, rect.top - 24), | ||
| width: rect.width, | ||
| }); | ||
| }; | ||
|
|
||
| updatePosition(); | ||
| window.addEventListener("resize", updatePosition); | ||
| window.addEventListener("scroll", updatePosition, true); | ||
|
|
||
| const observer = | ||
| typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updatePosition); | ||
| observer?.observe(anchor); | ||
|
|
||
| return () => { | ||
| observer?.disconnect(); | ||
| window.removeEventListener("resize", updatePosition); | ||
| window.removeEventListener("scroll", updatePosition, true); | ||
| }; | ||
| }, [props.anchor]); | ||
|
|
||
| if (!position) return null; | ||
|
|
||
| return createPortal( | ||
| <div | ||
| className="pointer-events-auto fixed z-[70]" | ||
| style={{ | ||
| bottom: position.bottom, | ||
| left: position.left, | ||
| maxHeight: position.maxHeight, | ||
| width: position.width, | ||
| }} | ||
| > | ||
| {props.children} | ||
| </div>, | ||
| document.body, | ||
| ); | ||
| } | ||
| import { Button } from "../ui/button"; | ||
| import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; | ||
| import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | ||
|
|
@@ -903,6 +963,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | |
| const [isComposerPrimaryActionsCompact, setIsComposerPrimaryActionsCompact] = useState(false); | ||
| const [isComposerModelPickerOpen, setIsComposerModelPickerOpen] = useState(false); | ||
| const [isComposerFocused, setIsComposerFocused] = useState(false); | ||
| const [composerMenuAnchor, setComposerMenuAnchor] = useState<HTMLDivElement | null>(null); | ||
| const isMobileViewport = useMediaQuery("max-sm"); | ||
| const isComposerCollapsedMobile = | ||
| isMobileViewport && !forceExpandedOnMobile && !isComposerFocused; | ||
|
|
@@ -2337,14 +2398,15 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | |
| ) : null} | ||
|
|
||
| <div | ||
| ref={setComposerMenuAnchor} | ||
| className={cn( | ||
| "relative px-3 pb-2 sm:px-4", | ||
| hasComposerHeader ? "pt-2.5 sm:pt-3" : "pt-3.5 sm:pt-4", | ||
| isComposerCollapsedMobile && "hidden", | ||
| )} | ||
| > | ||
| {composerMenuOpen && !isComposerApprovalState && ( | ||
| <div className="absolute inset-x-0 bottom-full z-20 mb-2"> | ||
| <ComposerCommandMenuLayer anchor={composerMenuAnchor}> | ||
| <ComposerCommandMenu | ||
| items={composerMenuItems} | ||
| resolvedTheme={resolvedTheme} | ||
|
|
@@ -2359,7 +2421,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | |
| onHighlightedItemChange={onComposerMenuItemHighlighted} | ||
| onSelect={onSelectComposerItem} | ||
| /> | ||
| </div> | ||
| </ComposerCommandMenuLayer> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Portaled menu survives mobile collapseMedium Severity The portaled command menu mispositions when its anchor element becomes hidden on mobile composer collapse. This occurs because Reviewed by Cursor Bugbot for commit a9aeb0e. Configure here. |
||
| )} | ||
|
|
||
| {!isComposerCollapsedMobile && | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| const DIALOG_BACKDROP_CLASS = | ||
| "dialog-backdrop fixed inset-0 z-50 transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0"; | ||
|
|
||
| const DIALOG_POPUP_CLASS = | ||
| "dialog-glass -translate-y-[calc(1.25rem*var(--nested-dialogs))] relative flex min-h-0 w-full min-w-0 scale-[calc(1-0.1*var(--nested-dialogs))] flex-col rounded-2xl border opacity-[calc(1-0.1*var(--nested-dialogs))] outline-none transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0"; | ||
|
|
||
| const DIALOG_MOBILE_SHEET_CLASS = | ||
| "max-sm:max-w-none max-sm:rounded-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:opacity-[calc(1-min(var(--nested-dialogs),1))] max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4"; | ||
|
|
||
| export { DIALOG_BACKDROP_CLASS, DIALOG_MOBILE_SHEET_CLASS, DIALOG_POPUP_CLASS }; |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Composer menu above modal layers
Medium Severity
The portaled composer command menu's
z-indexof70places it above shared dialogs and command palette surfaces (z-50). This can cause the menu to render on top of these elements and intercept pointer events.Reviewed by Cursor Bugbot for commit a9aeb0e. Configure here.