Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/content/docs/react/styling-theming/themes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Here are each of the theme CSS variables you can set, with values from the defau
--bn-border-radius: 6px;
```

Setting these variables on the `.bn-container[data-color-scheme]` selector will overwrite them for both default light & dark themes. To overwrite variables separately for light & dark themes, use the `.bn-container[data-color-scheme="light"]` and `.bn-container[data-color-scheme="dark"]` selectors.
Setting these variables on the `.bn-root[data-color-scheme]` selector will overwrite them for both default light & dark themes. To overwrite variables separately for light & dark themes, use the `.bn-root[data-color-scheme="light"]` and `.bn-root[data-color-scheme="dark"]` selectors.

## Programmatic Configuration

Expand Down
11 changes: 9 additions & 2 deletions examples/01-basic/12-multi-editor/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";

// Component that creates & renders a BlockNote editor.
function Editor(props: { initialContent?: PartialBlock[] }) {
function Editor(props: {
initialContent?: PartialBlock[];
theme: "dark" | "light";
}) {
// Creates a new editor instance.
const editor = useCreateBlockNote({
initialContent: props.initialContent,
});

// Renders the editor instance using a React component.
return <BlockNoteView editor={editor} style={{ flex: 1 }} />;
return (
<BlockNoteView theme={props.theme} editor={editor} style={{ flex: 1 }} />
);
}

export default function App() {
// Creates & renders two editors side by side.
return (
<div style={{ display: "flex" }}>
<Editor
theme="dark"
initialContent={[
{
type: "paragraph",
Expand All @@ -35,6 +41,7 @@ export default function App() {
]}
/>
<Editor
theme="light"
initialContent={[
{
type: "paragraph",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function App() {
// additional class names/attributes depend on the UI library you're using,
// whether you want to show light or dark more, etc. It's easiest to just
// check the rendered editor HTML to see what you need to add.
<div className="bn-container bn-mantine">
<div className="bn-root bn-container bn-mantine">
<div
className="ProseMirror bn-editor bn-default-styles"
dangerouslySetInnerHTML={{ __html: html }}
Expand Down
2 changes: 1 addition & 1 deletion examples/04-theming/02-changing-font/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.bn-container[data-changing-font-demo] .bn-editor * {
.bn-root[data-changing-font-demo] .bn-editor * {
font-family: "Comic Sans MS", sans-serif;
}
7 changes: 3 additions & 4 deletions examples/04-theming/03-theming-css/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* Adds border and shadow to editor */
.bn-container[data-theming-css-demo] .bn-editor * {
.bn-root[data-theming-css-demo] .bn-editor * {
color: blue;
}

/* Makes slash menu hovered items blue */
.bn-container[data-theming-css-demo]
.bn-suggestion-menu-item[aria-selected="true"],
.bn-container[data-theming-css-demo] .bn-suggestion-menu-item:hover {
.bn-root[data-theming-css-demo] .bn-suggestion-menu-item[aria-selected="true"],
.bn-root[data-theming-css-demo] .bn-suggestion-menu-item:hover {
background-color: blue;
}
4 changes: 2 additions & 2 deletions examples/04-theming/04-theming-css-variables/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Base theme */
.bn-container[data-theming-css-variables-demo][data-color-scheme] {
.bn-root[data-theming-css-variables-demo][data-color-scheme] {
--bn-colors-editor-text: #222222;
--bn-colors-editor-background: #ffeeee;
--bn-colors-menu-text: #ffffff;
Expand All @@ -21,7 +21,7 @@
}

/* Changes for dark mode */
.bn-container[data-theming-css-variables-demo][data-color-scheme="dark"] {
.bn-root[data-theming-css-variables-demo][data-color-scheme="dark"] {
--bn-colors-editor-text: #ffffff;
--bn-colors-editor-background: #9b0000;
--bn-colors-side-menu: #ffffff;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export default function App() {
etc. It's easiest to just copy the class names and HTML attributes
from an actual BlockNote editor. */}
<div
className="bn-container bn-mantine"
className="bn-root bn-container bn-mantine"
data-color-scheme={theme}
data-mantine-color-scheme={theme}
>
Expand Down
5 changes: 3 additions & 2 deletions packages/ariakit/src/comments/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const Comment = forwardRef<
actions,
children,
edited,
emojiPickerOpen, // Unused

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi, this was not implemented in ariakit / shadcn, so decided to fix

emojiPickerOpen,
...rest
} = props;

Expand All @@ -72,7 +72,8 @@ export const Comment = forwardRef<
(showActions === true ||
showActions === undefined ||
(showActions === "hover" && hovered) ||
focused);
focused ||
emojiPickerOpen);

return (
<AriakitGroup
Expand Down
15 changes: 12 additions & 3 deletions packages/ariakit/src/popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {

import { assertEmpty, mergeCSSClasses } from "@blocknote/core";
import { ComponentProps } from "@blocknote/react";
import { forwardRef } from "react";
import { createContext, forwardRef, useContext } from "react";

const PortalRootContext = createContext<HTMLElement | null | undefined>(
undefined,
);

export const PopoverTrigger = forwardRef<
HTMLButtonElement,
Expand All @@ -27,13 +31,16 @@ export const PopoverContent = forwardRef<

assertEmpty(rest);

const portalRoot = useContext(PortalRootContext);

return (
<AriakitPopover
className={mergeCSSClasses(
"bn-ak-popover",
className || "",
variant === "panel-popover" ? "bn-ak-panel-popover" : "",
)}
portalElement={portalRoot ?? undefined}
ref={ref}
>
{children}
Expand All @@ -44,7 +51,7 @@ export const PopoverContent = forwardRef<
export const Popover = (
props: ComponentProps["Generic"]["Popover"]["Root"],
) => {
const { children, open, onOpenChange, position, ...rest } = props;
const { children, open, onOpenChange, position, portalRoot, ...rest } = props;

assertEmpty(rest);

Expand All @@ -54,7 +61,9 @@ export const Popover = (
setOpen={onOpenChange}
placement={position}
>
{children}
<PortalRootContext.Provider value={portalRoot}>
{children}
</PortalRootContext.Provider>
</AriakitPopoverProvider>
);
};
20 changes: 0 additions & 20 deletions packages/core/src/editor/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,6 @@
padding: 0;
}

/*

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was unused, lingering code that I removed in this PR

bn-root should be applied to all top-level elements

This includes the Prosemirror editor, but also <div> element such as
Tippy popups that are appended to document.body directly
*/
.bn-root {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

.bn-root *,
.bn-root *::before,
.bn-root *::after {
-webkit-box-sizing: inherit;
-moz-box-sizing: inherit;
box-sizing: inherit;
}

/* reset styles, they will be set on blockContent */
.bn-default-styles p,
.bn-default-styles h1,
Expand Down
8 changes: 2 additions & 6 deletions packages/core/src/extensions/SideMenu/SideMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,7 @@ export class SideMenuView<
this.mousePos.y > editorOuterBoundingBox.top &&
this.mousePos.y < editorOuterBoundingBox.bottom;

// TODO: remove parentElement, but then we need to remove padding from boundingbox or find a different solution
const editorWrapper = this.pmView.dom!.parentElement!;
const closestBNRoot = (event.target as HTMLElement).closest(".bn-root");

// Doesn't update if the mouse hovers an element that's over the editor but
// isn't a part of it or the side menu.
Expand All @@ -624,10 +623,7 @@ export class SideMenuView<
event &&
event.target &&
// Element is outside the editor
!(
editorWrapper === event.target ||
editorWrapper.contains(event.target as HTMLElement)
)
!closestBNRoot
) {
if (this.state?.show) {
this.state.show = false;
Expand Down
7 changes: 4 additions & 3 deletions packages/mantine/src/popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ import { forwardRef } from "react";
export const Popover = (
props: ComponentProps["Generic"]["Popover"]["Root"],
) => {
const { open, onOpenChange, position, children, ...rest } = props;
const { open, onOpenChange, position, portalRoot, children, ...rest } = props;

assertEmpty(rest);

return (
<MantinePopover
middlewares={{ size: { padding: 20 } }}
withinPortal={false}
withinPortal={!!portalRoot}
portalProps={portalRoot ? { target: portalRoot } : undefined}
opened={open}
onChange={onOpenChange}
position={position}
zIndex={10000}
zIndex={portalRoot ? undefined : 10000}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify z-index handling for Mantine popovers and bn root variable wiring.

# 1) Confirm changed Mantine popover behavior in this file.
rg -nP -C3 'withinPortal=|portalProps=|zIndex=' packages/mantine/src/popover/Popover.tsx

# 2) Check whether bn-ui-base-z-index is consumed anywhere in styling/code.
rg -nP -C2 'bn-ui-base-z-index|--bn-ui-base-z-index|z-index' packages/react packages/mantine

# 3) Inspect portal root creation context for explicit stacking styles.
rg -nP -C8 'createPortal\\(|className=|data-color-scheme|bn-root' packages/react/src/editor/BlockNoteView.tsx

Repository: TypeCellOS/BlockNote

Length of output: 2993


🏁 Script executed:

# Find the portal root creation and its styling context
rg -nA15 -B5 'createPortal|portalRoot.*=' packages/react/src/editor/BlockNoteView.tsx | head -100

Repository: TypeCellOS/BlockNote

Length of output: 2391


🏁 Script executed:

# Check if there's z-index styling on the portal root or bn-root specifically
rg -nP 'bn-root|portal.*root' packages/react/src/editor/styles.css packages/react/src/editor/BlockNoteView.tsx -A3 -B3

Repository: TypeCellOS/BlockNote

Length of output: 4550


🏁 Script executed:

# Check Mantine Popover implementation or types to see how zIndex behaves
fd -t f -e tsx -e ts packages/mantine | xargs grep -l "zIndex" | head -5

Repository: TypeCellOS/BlockNote

Length of output: 433


🏁 Script executed:

# Find Mantine Popover files
fd -t f --full-path 'packages/mantine' -e tsx -e ts | head -20

Repository: TypeCellOS/BlockNote

Length of output: 856


🏁 Script executed:

# Check if there's any handling of zIndex prop for Popover in Mantine code
rg -n 'zIndex|z-index' packages/mantine/src/popover/ -B3 -A3

Repository: TypeCellOS/BlockNote

Length of output: 538


🏁 Script executed:

# Verify what Mantine's default z-index behavior is for portaled popovers
rg -n 'const.*Popover|export.*Popover' packages/mantine/src/popover/ -A20 | head -80

Repository: TypeCellOS/BlockNote

Length of output: 3408


Portaled popovers lose stacking control with Mantine's default z-index.

At line 26, zIndex is set to undefined when portalRoot exists. The portal root (created in packages/react/src/editor/BlockNoteView.tsx:217-226) has no explicit z-index styling and inherits --bn-ui-base-z-index: 0 from the .bn-root CSS rule. This causes Mantine to apply its default z-index (300 via CSS variables), which can fall behind fixed/sticky app chrome without explicit stacking control.

The fix aligns with the established pattern used in GenericPopover.tsx and other UI components, which leverage the --bn-ui-base-z-index CSS variable for consistent z-index management:

Proposed fix
-      zIndex={portalRoot ? undefined : 10000}
+      zIndex={portalRoot ? "var(--bn-ui-base-z-index, 10000)" : 10000}

This ensures portaled popovers respect the base z-index system while maintaining fallback to 10000 when the variable is not defined.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
zIndex={portalRoot ? undefined : 10000}
zIndex={portalRoot ? "var(--bn-ui-base-z-index, 10000)" : 10000}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mantine/src/popover/Popover.tsx` at line 26, Popover.tsx currently
clears zIndex when portalRoot exists which lets Mantine's default z-index win;
change the zIndex logic to use the CSS variable fallback pattern from
GenericPopover.tsx by assigning zIndex to the CSS variable --bn-ui-base-z-index
with a 10000 fallback when portalRoot is truthy (and keep 10000 when not
portaled) so portaled popovers respect the app's base z-index system; update the
zIndex prop usage in the Popover component (the zIndex prop set at the
repository diff line) accordingly to use the var(--bn-ui-base-z-index, 10000)
approach.

>
{children}
</MantinePopover>
Expand Down
45 changes: 19 additions & 26 deletions packages/react/src/components/Comments/EmojiPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { ReactNode, useState } from "react";
import { useComponentsContext } from "../../editor/ComponentsContext.js";
import { useBlockNoteContext } from "../../editor/BlockNoteContext.js";
import Picker from "./EmojiMartPicker.js";
import { createPortal } from "react-dom";
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js";

export const EmojiPicker = (props: {
onEmojiSelect: (emoji: { native: string }) => void;
Expand All @@ -14,11 +12,10 @@ export const EmojiPicker = (props: {
const [open, setOpen] = useState(false);

const Components = useComponentsContext()!;
const editor = useBlockNoteEditor();
const blockNoteContext = useBlockNoteContext();

return (
<Components.Generic.Popover.Root open={open}>
<Components.Generic.Popover.Root open={open} portalRoot={blockNoteContext?.portalRoot}>
<Components.Generic.Popover.Trigger>
<div
onClick={(event) => {
Expand All @@ -39,28 +36,24 @@ export const EmojiPicker = (props: {
{props.children}
</div>
</Components.Generic.Popover.Trigger>
{editor.domElement?.parentElement &&
createPortal(
<Components.Generic.Popover.Content
className={"bn-emoji-picker-popover"}
variant={"panel-popover"}
>
<Picker
perLine={7}
onClickOutside={() => {
setOpen(false);
props.onOpenChange?.(false);
}}
onEmojiSelect={(emoji: { native: string }) => {
props.onEmojiSelect(emoji);
setOpen(false);
props.onOpenChange?.(false);
}}
theme={blockNoteContext?.colorSchemePreference}
/>
</Components.Generic.Popover.Content>,
editor.domElement.parentElement,
)}
<Components.Generic.Popover.Content
className={"bn-emoji-picker-popover"}
variant={"panel-popover"}
>
<Picker
perLine={7}
onClickOutside={() => {
setOpen(false);
props.onOpenChange?.(false);
}}
onEmojiSelect={(emoji: { native: string }) => {
props.onEmojiSelect(emoji);
setOpen(false);
props.onOpenChange?.(false);
}}
theme={blockNoteContext?.colorSchemePreference}
/>
</Components.Generic.Popover.Content>
</Components.Generic.Popover.Root>
);
};
39 changes: 25 additions & 14 deletions packages/react/src/components/Popovers/GenericPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
autoUpdate,
FloatingFocusManager,
FloatingPortal,
useDismiss,
useFloating,
useHover,
Expand All @@ -11,6 +12,7 @@ import {
} from "@floating-ui/react";
import { HTMLAttributes, ReactNode, useEffect, useRef } from "react";

import { useBlockNoteContext } from "../../editor/BlockNoteContext.js";
import { FloatingUIOptions } from "./FloatingUIOptions.js";

export type GenericPopoverReference =
Expand Down Expand Up @@ -83,6 +85,9 @@ export const GenericPopover = (
children: ReactNode;
},
) => {
const blockNoteContext = useBlockNoteContext();
const portalRoot = blockNoteContext?.portalRoot ?? undefined;

const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({
whileElementsMounted: autoUpdate,
...props.useFloatingOptions,
Expand Down Expand Up @@ -152,7 +157,7 @@ export const GenericPopover = (
style: {
display: "flex",
...props.elementProps?.style,
zIndex: `calc(var(--bn-ui-base-z-index) + ${props.elementProps?.style?.zIndex || 0})`,
zIndex: `calc(var(--bn-ui-base-z-index, 0) + ${props.elementProps?.style?.zIndex || 0})`,
...floatingStyles,
...styles,
},
Expand All @@ -169,27 +174,33 @@ export const GenericPopover = (
// should be open. So without this fix, the popover just won't transition
// out and will instead appear to hide instantly.
return (
<div
ref={mergedRefs}
{...mergedProps}
dangerouslySetInnerHTML={{ __html: innerHTML.current }}
/>
<FloatingPortal root={portalRoot}>
<div
ref={mergedRefs}
{...mergedProps}
dangerouslySetInnerHTML={{ __html: innerHTML.current }}
/>
</FloatingPortal>
);
}

if (!props.focusManagerProps?.disabled) {
return (
<FloatingFocusManager {...props.focusManagerProps} context={context}>
<div ref={mergedRefs} {...mergedProps}>
{props.children}
</div>
</FloatingFocusManager>
<FloatingPortal root={portalRoot}>
<FloatingFocusManager {...props.focusManagerProps} context={context}>
<div ref={mergedRefs} {...mergedProps}>
{props.children}
</div>
</FloatingFocusManager>
</FloatingPortal>
);
}

return (
<div ref={mergedRefs} {...mergedProps}>
{props.children}
</div>
<FloatingPortal root={portalRoot}>
<div ref={mergedRefs} {...mergedProps}>
{props.children}
</div>
</FloatingPortal>
);
};
Loading
Loading