From 83a0b63a52ed8f0cda56cb2e4533c22196904013 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 8 Jun 2026 02:11:03 +0000 Subject: [PATCH] Lazy-load the date picker to keep Radix out of the editor schema graph Importing @radix-ui/react-popover and react-day-picker at the top of Mentions.tsx pulled them into the editor schema's static import graph, which is also loaded on the server. Radix's prebuilt ESM does a bare "react/jsx-runtime" import that the node/shared test resolvers can't resolve, breaking all server and shared editor test suites. Move the popover + calendar into DateMentionPicker, loaded via React.lazy, so the browser-only dependencies are code-split out of the schema graph and only fetched when an editable date mention renders. --- .../editor/components/DateMentionPicker.tsx | 100 ++++++++++++++++++ shared/editor/components/Mentions.tsx | 82 +++----------- 2 files changed, 113 insertions(+), 69 deletions(-) create mode 100644 shared/editor/components/DateMentionPicker.tsx diff --git a/shared/editor/components/DateMentionPicker.tsx b/shared/editor/components/DateMentionPicker.tsx new file mode 100644 index 0000000000..cf2ad1e9c4 --- /dev/null +++ b/shared/editor/components/DateMentionPicker.tsx @@ -0,0 +1,100 @@ +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import { RemoveScroll } from "react-remove-scroll"; +import styled from "styled-components"; +import { Calendar } from "../../components/Calendar"; +import { depths, s } from "../../styles"; +import { dateLocale, toISODate } from "../../utils/date"; + +type Props = { + /** The currently selected date, if any. */ + selectedDate?: Date; + /** The user's language, used to localise the calendar. */ + language?: Parameters[0]; + /** Called with the new date-only ISO string when a day is picked. */ + onChange: (modelId: string) => void; + /** The trigger element the calendar popover is anchored to. */ + children: React.ReactNode; +}; + +/** + * The interactive calendar popover for a date mention. It lives in its own + * module so that its browser-only dependencies (Radix, react-day-picker) are + * loaded lazily and stay out of the editor schema graph, which is also imported + * on the server. + * + * @returns the popover wrapping the provided trigger. + */ +export default function DateMentionPicker({ + selectedDate, + language, + onChange, + children, +}: Props) { + const { t } = useTranslation(); + const [open, setOpen] = React.useState(false); + + const handleSelect = React.useCallback( + (date: Date) => { + setOpen(false); + onChange(toISODate(date)); + }, + [onChange] + ); + + return ( + + e.stopPropagation()} + > + {children} + + + e.preventDefault()} + > + + + + + + + + + ); +} + +const DatePopoverContent = styled.div` + z-index: ${depths.modal}; + background: ${s("menuBackground")}; + box-shadow: ${s("menuShadow")}; + border-radius: 8px; + outline: none; + + &[data-state="open"] { + animation: fadeIn 150ms ease; + } + + @keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +`; diff --git a/shared/editor/components/Mentions.tsx b/shared/editor/components/Mentions.tsx index 2d6383f9fa..83a7a0d03d 100644 --- a/shared/editor/components/Mentions.tsx +++ b/shared/editor/components/Mentions.tsx @@ -1,5 +1,3 @@ -import * as PopoverPrimitive from "@radix-ui/react-popover"; -import { Slot } from "@radix-ui/react-slot"; import { observer } from "mobx-react"; import { DocumentIcon, @@ -10,18 +8,10 @@ import { import type { Node } from "prosemirror-model"; import * as React from "react"; import { useTranslation } from "react-i18next"; -import { RemoveScroll } from "react-remove-scroll"; import { Link } from "react-router-dom"; import styled from "styled-components"; -import { depths, s } from "../../styles"; -import { - dateLocale, - dateToRelativeReadable, - parseISODate, - toISODate, -} from "../../utils/date"; +import { dateToRelativeReadable, parseISODate } from "../../utils/date"; import { Backticks } from "../../components/Backticks"; -import { Calendar } from "../../components/Calendar"; import Flex from "../../components/Flex"; import Icon from "../../components/Icon"; import { IssueStatusIcon } from "../../components/IssueStatusIcon"; @@ -525,11 +515,15 @@ type DateProps = ComponentProps & { onChangeDate: (modelId: string) => void; }; +// Loaded lazily so its browser-only dependencies (Radix, react-day-picker) +// don't enter the editor schema's static import graph, which is also used on +// the server. +const DateMentionPicker = React.lazy(() => import("./DateMentionPicker")); + export const MentionDate = observer(function MentionDate_(props: DateProps) { const { isSelected, isEditable, node, onChangeDate } = props; const { t } = useTranslation(); const { auth } = useStores(); - const [open, setOpen] = React.useState(false); const { className, unfurl, ...attrs } = getAttributesFromNode(node); const language = auth.user?.language; @@ -537,14 +531,6 @@ export const MentionDate = observer(function MentionDate_(props: DateProps) { const display = dateToRelativeReadable(iso, t, language); const selectedDate = parseISODate(iso) ?? undefined; - const handleSelect = React.useCallback( - (date: Date) => { - setOpen(false); - onChangeDate(toISODate(date)); - }, - [onChangeDate] - ); - const content = ( - e.stopPropagation()} + + {content} - - - e.preventDefault()} - > - - - - - - - - + + ); }); @@ -622,27 +587,6 @@ const DateMention = styled.span<{ $editable: boolean }>` user-select: none; `; -const DatePopoverContent = styled.div` - z-index: ${depths.modal}; - background: ${s("menuBackground")}; - box-shadow: ${s("menuShadow")}; - border-radius: 8px; - outline: none; - - &[data-state="open"] { - animation: fadeIn 150ms ease; - } - - @keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } -`; - const StyledWarningIcon = styled(WarningIcon)` margin: 0 -2px; `;