+
{
if (activeId) {
- props.setOpenTab(activeId);
+ setOpenTab(activeId);
}
}}>
- {/*{props.loading && }*/}
+ {/*{loading && }*/}
-
- {props.tabs.map((tab) => (
-
+
+ {tabs.map((tab) => (
+
{tab.name}
))}
- {props.tabs.map((tab) => (
-
- {tab.tabPanel}
-
- ))}
+
+ {tabs.map((tab) => (
+
+ {tab.tabPanel}
+
+ ))}
+
);
-};
+});
diff --git a/packages/ariakit/src/panel/PanelButton.tsx b/packages/ariakit/src/panel/PanelButton.tsx
index b9c04f511f..04c300c18d 100644
--- a/packages/ariakit/src/panel/PanelButton.tsx
+++ b/packages/ariakit/src/panel/PanelButton.tsx
@@ -1,16 +1,21 @@
import * as Ariakit from "@ariakit/react";
import { mergeCSSClasses } from "@blocknote/core";
-import { PanelButtonProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const PanelButton = (props: PanelButtonProps) => {
- const { children, className, ...rest } = props;
+export const PanelButton = forwardRef<
+ HTMLButtonElement,
+ ComponentProps["ImagePanel"]["Button"]
+>((props, ref) => {
+ const { className, children, onClick } = props;
return (
+ className={mergeCSSClasses("bn-ak-button", className || "")}
+ onClick={onClick}
+ ref={ref}>
{children}
);
-};
+});
diff --git a/packages/ariakit/src/panel/PanelFileInput.tsx b/packages/ariakit/src/panel/PanelFileInput.tsx
index 34cc47a7a8..36df131840 100644
--- a/packages/ariakit/src/panel/PanelFileInput.tsx
+++ b/packages/ariakit/src/panel/PanelFileInput.tsx
@@ -1,16 +1,25 @@
import * as Ariakit from "@ariakit/react";
-import { PanelFileInputProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const PanelFileInput = (props: PanelFileInputProps) => (
-
- props.onChange?.(e.target.files![0])}
- placeholder={props.placeholder}
- />
-
-);
+export const PanelFileInput = forwardRef<
+ HTMLInputElement,
+ ComponentProps["ImagePanel"]["FileInput"]
+>((props, ref) => {
+ const { className, value, placeholder, onChange } = props;
+
+ return (
+
+ onChange?.(e.target.files![0])}
+ placeholder={placeholder}
+ />
+
+ );
+});
diff --git a/packages/ariakit/src/panel/PanelTab.tsx b/packages/ariakit/src/panel/PanelTab.tsx
index 8a689d075f..58ed2aba46 100644
--- a/packages/ariakit/src/panel/PanelTab.tsx
+++ b/packages/ariakit/src/panel/PanelTab.tsx
@@ -1,14 +1,15 @@
-import { mergeCSSClasses } from "@blocknote/core";
-import { PanelTabProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const PanelTab = (props: PanelTabProps) => {
- const { className, children, ...rest } = props;
+export const PanelTab = forwardRef<
+ HTMLDivElement,
+ ComponentProps["ImagePanel"]["TabPanel"]
+>((props, ref) => {
+ const { className, children } = props;
return (
-
+
{children}
);
-};
+});
diff --git a/packages/ariakit/src/panel/PanelTextInput.tsx b/packages/ariakit/src/panel/PanelTextInput.tsx
index a0e5163cf3..3e7f9c2ac4 100644
--- a/packages/ariakit/src/panel/PanelTextInput.tsx
+++ b/packages/ariakit/src/panel/PanelTextInput.tsx
@@ -1,18 +1,27 @@
import * as Ariakit from "@ariakit/react";
-import { PanelTextInputProps } from "@blocknote/react";
+import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const PanelTextInput = (props: PanelTextInputProps) => {
- const { children, ...rest } = props;
+export const PanelTextInput = forwardRef<
+ HTMLInputElement,
+ ComponentProps["ImagePanel"]["TextInput"]
+>((props, ref) => {
+ const { className, value, placeholder, onKeyDown, onChange } = props;
return (
);
-};
+});
diff --git a/packages/ariakit/src/popover/Popover.tsx b/packages/ariakit/src/popover/Popover.tsx
index b853cc94e3..71adb6f07f 100644
--- a/packages/ariakit/src/popover/Popover.tsx
+++ b/packages/ariakit/src/popover/Popover.tsx
@@ -1,27 +1,41 @@
import * as Ariakit from "@ariakit/react";
import { mergeCSSClasses } from "@blocknote/core";
-import { forwardRef, HTMLAttributes } from "react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
export const PopoverTrigger = forwardRef<
HTMLButtonElement,
- HTMLAttributes
+ ComponentProps["Generic"]["Popover"]["Trigger"]
>((props, ref) => {
- const { className, children, ...rest } = props;
+ const { children } = props;
+
+ return ;
+});
+
+export const PopoverContent = forwardRef<
+ HTMLDivElement,
+ ComponentProps["Generic"]["Popover"]["Content"]
+>((props, ref) => {
+ const { className, children } = props;
return (
-
+
+ {children}
+
);
});
-export const PopoverContent = forwardRef(
- (props, ref) =>
-);
+export const Popover = (
+ props: ComponentProps["Generic"]["Popover"]["Root"]
+) => {
+ const { children, opened, position } = props;
-export const Popover = (props: Ariakit.PopoverProviderProps) => (
-
-);
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/ariakit/src/sideMenu/SideMenu.tsx b/packages/ariakit/src/sideMenu/SideMenu.tsx
new file mode 100644
index 0000000000..742112d71c
--- /dev/null
+++ b/packages/ariakit/src/sideMenu/SideMenu.tsx
@@ -0,0 +1,17 @@
+import * as Ariakit from "@ariakit/react";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SideMenu = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SideMenu"]["Root"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ariakit/src/sideMenu/SideMenuButton.tsx b/packages/ariakit/src/sideMenu/SideMenuButton.tsx
new file mode 100644
index 0000000000..60ed1b7675
--- /dev/null
+++ b/packages/ariakit/src/sideMenu/SideMenuButton.tsx
@@ -0,0 +1,25 @@
+import * as Ariakit from "@ariakit/react";
+
+import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SideMenuButton = forwardRef<
+ HTMLButtonElement,
+ ComponentProps["SideMenu"]["Button"]
+>((props, ref) => {
+ const { className, children, icon, onClick } = props;
+
+ return (
+
+ {icon}
+ {children}
+
+ );
+});
diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css
index 61493d7e06..974dffbb29 100644
--- a/packages/ariakit/src/style.css
+++ b/packages/ariakit/src/style.css
@@ -1,399 +1,125 @@
-.button {
- display: flex;
- height: 2.5rem;
- user-select: none;
- align-items: center;
- white-space: nowrap;
- border-style: none;
- background-color: hsl(204 20% 100%);
- padding-left: 1rem;
- padding-right: 1rem;
- font-size: 1rem;
- line-height: 1.5rem;
- color: hsl(204 4% 0%);
- text-decoration-line: none;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: hsl(204 100% 40%);
- --border: rgba(0, 0, 0, 0.1);
- --highlight: rgba(255, 255, 255, 0.2);
- --shadow: rgba(0, 0, 0, 0.1);
- box-shadow: inset 0 0 0 1px var(--border), inset 0 2px 0 var(--highlight),
- inset 0 -1px 0 var(--shadow), 0 1px 1px var(--shadow);
- scroll-margin-left: 0.25rem;
- scroll-margin-right: 0.25rem;
- gap: 0.5rem;
- border-radius: 0.25rem;
- justify-content: space-between;
- width: auto;
-}
+@import url("@blocknote/react/style.css");
-.button:hover {
- background-color: hsl(204 20% 96%);
-}
+/* This file contains BlockNote-specific fixes for the styles in
+ ariakitStyles.css. */
-.button[aria-disabled="true"] {
- opacity: 0.5;
-}
+@import "./ariakitStyles.css";
-.button[aria-expanded="true"] {
- background-color: hsl(204 20% 96%);
+.bn-ak-input-wrapper {
+ align-items: center;
+ display: flex;
+ gap: 0.5rem;
}
-.button[data-focus-visible] {
- outline-style: solid;
+.bn-toolbar .bn-ak-button {
+ width: unset;
}
-.button:active,
-.button[data-active] {
+.bn-toolbar .bn-ak-button[data-selected] {
padding-top: 0.125rem;
- box-shadow: inset 0 0 0 1px var(--border), inset 0 2px 0 var(--border);
-}
-
-:is(.dark .button) {
- background-color: hsl(204 20% 100% / 0.05);
- color: hsl(204 20% 100%);
- --shadow: rgba(0, 0, 0, 0.25);
- --border: rgba(255, 255, 255, 0.1);
- --highlight: rgba(255, 255, 255, 0.05);
- box-shadow: inset 0 0 0 1px var(--border), inset 0 -1px 0 1px var(--shadow),
- inset 0 1px 0 var(--highlight);
+ box-shadow: inset 0 0 0 1px var(--border),
+ inset 0 2px 0 var(--border);
}
-:is(.dark .button:hover) {
- background-color: hsl(204 20% 100% / 0.1);
+.bn-toolbar .bn-ak-button[data-selected]:where(.dark, .dark *) {
+ box-shadow: inset 0 0 0 1px var(--border),
+ inset 0 1px 1px 1px var(--shadow);
}
-:is(.dark .button)[aria-expanded="true"] {
- background-color: hsl(204 20% 100% / 0.1);
-}
-
-:is(.dark .button:active),
-:is(.dark .button[data-active]) {
- box-shadow: inset 0 0 0 1px var(--border), inset 0 1px 1px 1px var(--shadow);
-}
-
-@media (min-width: 640px) {
- .button {
- gap: 0.5rem;
- }
-}
-
-.secondary {
- background-color: transparent;
- color: currentColor;
- box-shadow: none;
-}
-
-.secondary:hover {
- background-color: hsl(204 4% 0% / 0.05);
-}
-
-:is(.dark .secondary) {
- background-color: transparent;
- box-shadow: none;
-}
-
-:is(.dark .secondary:hover) {
- background-color: hsl(204 20% 100% / 0.05);
-}
-
-:is(.dark .secondary:active),
-:is(.dark .secondary[data-active]) {
- box-shadow: none;
+.bn-toolbar .bn-ak-popover {
+ gap: 0.5rem;
}
-.bn-toolbar {
- display: flex;
- max-width: 100%;
+.bn-tab-panel {
align-items: center;
- gap: 0.25rem;
- overflow-x: auto;
- border-radius: 0.5rem;
- background-color: hsl(204 20% 100%);
- padding: 0.25rem;
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
-}
-
-:is(.dark .bn-toolbar) {
- background-color: hsl(204 4% 16%);
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.25), 0 1px 2px -1px rgb(0 0 0 / 0.1);
-}
-
-.separator {
- height: 2rem;
- border-right-width: 1px;
- border-color: hsl(204 20% 88%);
-}
-
-:is(.dark .separator) {
- border-color: hsl(204 4% 28%);
-}
-
-.popover {
- z-index: 50;
display: flex;
- max-height: min(var(--popover-available-height, 300px), 300px);
flex-direction: column;
- overflow: auto;
- overscroll-behavior: contain;
- border-radius: 0.5rem;
- border-width: 1px;
- border-style: solid;
- border-color: hsl(204 20% 88%);
- background-color: hsl(204 20% 100%);
- padding: 0.5rem;
- color: hsl(204 4% 0%);
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
-}
-
-.popover:focus-visible,
-.popover[data-focus-visible] {
- outline: 2px solid hsl(204 100% 40%);
- outline-offset: -1px;
-}
-
-:is(.dark .popover) {
- border-color: hsl(204 4% 24%);
- background-color: hsl(204 4% 16%);
- color: hsl(204 20% 100%);
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.25),
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
-}
-
-.select-item {
- display: flex;
- cursor: default;
- scroll-margin: 0.5rem;
- align-items: center;
gap: 0.5rem;
- border-radius: 0.25rem;
- padding: 0.5rem;
- outline: none !important;
- padding-right: 2rem;
}
-.select-item[aria-disabled="true"] {
- opacity: 0.5;
+.bn-file-input {
+ max-width: 100%;
}
-.select-item[data-active-item] {
+.bn-ak-menu-item[data-hovered] {
background-color: hsl(204 100% 40%);
color: hsl(204 20% 100%);
}
-.input {
- height: 2.5rem;
- width: 100%;
- border-radius: 0.375rem;
- border-style: none;
- background-color: hsl(204 20% 94% / 0.4);
- padding-left: 1rem;
- padding-right: 1rem;
- font-size: 1rem;
- line-height: 1.5rem;
- color: hsl(204 4% 0%);
- box-shadow:
- inset 0 0 0 1px rgba(0 0 0 / 0.1),
- inset 0 2px 5px 0 rgba(0 0 0 / 0.05);
-}
-
-.input::placeholder {
- color: hsl(204 4% 0% / 0.6);
-}
-
-.input:hover {
- background-color: hsl(204 20% 94%);
+.bn-ak-menu-item {
+ display: flex;
}
-.input:focus-visible,
-.input[data-focus-visible] {
- outline: 2px solid hsl(204 100% 40%);
- outline-offset: -1px;
+.bn-dropdown {
+ overflow: visible;
}
-:is(.dark .input) {
- background-color: hsl(204 4% 10%);
- color: hsl(204 20% 100%);
- box-shadow:
- inset 0 0 0 1px rgba(255 255 255 / 0.12),
- inset 0 -1px 0 0 rgba(255 255 255 / 0.05),
- inset 0 2px 5px 0 rgba(0 0 0 / 0.15);
+.bn-suggestion-menu, .bn-color-picker-dropdown {
+ overflow: scroll;
}
-:is(.dark .input)::placeholder {
- color: hsl(204 20% 100% / 46%);
+.bn-ak-suggestion-menu-item-body {
+ flex: 1;
}
-:is(.dark .input:hover) {
- background-color: hsl(204 4% 8%);
+.bn-ak-suggestion-menu-item-subtitle {
+ font-size: 0.7rem;
}
-:is(.dark .bn-image-panel) {
- background-color: hsl(204 4% 16%);
- box-shadow:
- 0 1px 3px 0 rgb(0 0 0 / 0.25),
- 0 1px 2px -1px rgb(0 0 0 / 0.1);
+.bn-ak-suggestion-menu-item-section[data-position="left"] {
+ padding: 8px;
}
-.tab-list {
- display: flex;
- gap: 0.5rem;
+.bn-ak-suggestion-menu-item-section[data-position="right"] {
+ --border: rgb(0 0 0/13%);
+ --highlight: rgb(255 255 255/20%);
+ --shadow: rgb(0 0 0/10%);
+ box-shadow: inset 0 0 0 1px var(--border),
+ inset 0 2px 0 var(--highlight),
+ inset 0 -1px 0 var(--shadow),
+ 0 1px 1px var(--shadow);
+ font-size: 0.7rem;
+ border-radius: 4px;
+ padding-inline: 4px;
}
-.tab {
- display: flex;
- height: 2.5rem;
- user-select: none;
+.bn-side-menu {
align-items: center;
+ display: flex;
justify-content: center;
- gap: 0.5rem;
- white-space: nowrap;
- border-radius: 0.25rem;
- border-style: none;
- padding-left: 1rem;
- padding-right: 1rem;
- font-size: 1rem;
- line-height: 1.5rem;
- text-decoration-line: none;
- outline-width: 2px;
- outline-offset: 2px;
- outline-color: hsl(204 100% 40%);
-}
-
-.tab:hover {
- background-color: hsl(204 4% 0% / 7.5%);
-}
-
-.tab[aria-disabled="true"] {
- opacity: 0.5;
-}
-
-.tab[aria-selected="true"] {
- background-color: hsl(204 100% 40%);
- color: hsl(204 20% 100%);
-}
-
-.tab:hover[aria-selected="true"] {
- background-color: hsl(204 100% 32%);
-}
-
-.tab[data-focus-visible] {
- outline-style: solid;
-}
-
-.tab:active,
-.tab[data-active] {
- padding-top: 0.125rem;
-}
-
-:is(.dark .tab:hover) {
- background-color: hsl(204 20% 100% / 0.1);
-}
-
-:is(.dark .tab[aria-selected="true"]) {
- background-color: hsl(204 100% 40%);
- color: hsl(204 20% 100%);
}
-:is(.dark .tab:hover[aria-selected="true"]) {
- background-color: hsl(204 100% 32%);
+.bn-side-menu .bn-ak-button {
+ height: fit-content;
+ padding: 0;
+ width: fit-content;
}
-.panel {
- padding: 0.5rem;
-}
-
-.bn-menu {
- position: relative;
- z-index: 50;
- display: flex;
- max-height: var(--popover-available-height);
- min-width: 180px;
- flex-direction: column;
- overscroll-behavior: contain;
- border-radius: 0.5rem;
- border-width: 1px;
- border-style: solid;
- border-color: hsl(204 20% 88%);
- background-color: hsl(204 20% 100%);
- padding: 0.5rem;
- color: hsl(204 4% 0%);
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
- outline: none !important;
- overflow: visible;
-}
-
-:is(.dark .bn-menu) {
- border-color: hsl(204 4% 24%);
- background-color: hsl(204 4% 16%);
- color: hsl(204 20% 100%);
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.25),
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
+.bn-panel-popover {
+ background-color: transparent;
+ border: none;
+ box-shadow: none;
}
-.bn-menu-item {
- display: flex;
- cursor: default;
- scroll-margin: 0.5rem;
- align-items: center;
- gap: 0.5rem;
- border-radius: 0.25rem;
- padding: 0.5rem;
- outline: none !important;
+.bn-table-handle {
+ height: fit-content;
+ padding: 0;
+ width: fit-content;
}
-.bn-menu-item[aria-disabled="true"] {
- opacity: 0.25;
+.bn-side-menu, .bn-table-handle {
+ color: gray;
}
-.bn-menu-item[data-active-item] {
- background-color: hsl(204 100% 40%);
+.bn-ak-button:where(.dark, .dark *) {
color: hsl(204 20% 100%);
}
-.bn-menu-item:active,
-.bn-menu-item[data-active] {
- background-color: hsl(204 100% 32%);
- padding-top: 9px;
- padding-bottom: 7px;
-}
-
-.bn-menu:not(:focus) .bn-menu-item:not(:focus)[aria-expanded="true"] {
- background-color: hsl(204 4% 0% / 7.5%);
- color: currentColor;
-}
-
-:is(
- .dark .bn-menu:not(:focus) .bn-menu-item:not(:focus)[aria-expanded="true"]
- ) {
- background-color: hsl(204 20% 100% / 0.1);
-}
-
-.bn-menu-item .label {
- flex: 1 1 0%;
-}
-
-.bn-menu-label {
- cursor: default;
- padding: 0.5rem;
- padding-left: 0.75rem;
- padding-right: 0.75rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
- font-weight: 500;
- opacity: 0.5;
-}
-
-.bn-image-panel {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- border-radius: 0.5rem;
- background-color: hsl(204 20% 100%);
- padding: 0.5rem;
- box-shadow:
- 0 1px 3px 0 rgb(0 0 0 / 0.1),
- 0 1px 2px -1px rgb(0 0 0 / 0.1);
+.bn-ak-tab, .bn-file-input {
+ background-color: transparent;
+ color: black;
}
+.bn-ak-tab:where(.dark, .dark *), .bn-file-input:where(.dark, .dark *) {
+ color: white;
+}
\ No newline at end of file
diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenu.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenu.tsx
new file mode 100644
index 0000000000..4229250989
--- /dev/null
+++ b/packages/ariakit/src/suggestionMenu/SuggestionMenu.tsx
@@ -0,0 +1,20 @@
+import * as Ariakit from "@ariakit/react";
+
+import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenu = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Root"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuEmptyItem.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuEmptyItem.tsx
new file mode 100644
index 0000000000..9729f0685b
--- /dev/null
+++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuEmptyItem.tsx
@@ -0,0 +1,18 @@
+import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenuEmptyItem = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["EmptyItem"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ );
+});
diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx
new file mode 100644
index 0000000000..a6cdee6b26
--- /dev/null
+++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx
@@ -0,0 +1,61 @@
+import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef, useCallback } from "react";
+
+export const SuggestionMenuItem = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Item"]
+>((props, ref) => {
+ const {
+ className,
+ title,
+ subtext,
+ // group,
+ icon,
+ badge,
+ // aliases,
+ // onItemClick,
+ isSelected,
+ setSelected,
+ onClick,
+ } = props;
+
+ const handleMouseLeave = useCallback(() => {
+ setSelected?.(false);
+ }, [setSelected]);
+
+ const handleMouseEnter = useCallback(() => {
+ setSelected?.(true);
+ }, [setSelected]);
+
+ return (
+
+ {icon && (
+
+ {icon}
+
+ )}
+
+ {badge && (
+
+ )}
+
+ );
+});
diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuLabel.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuLabel.tsx
new file mode 100644
index 0000000000..63e77d2cab
--- /dev/null
+++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuLabel.tsx
@@ -0,0 +1,18 @@
+import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenuLabel = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Label"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx
new file mode 100644
index 0000000000..0b64e5496f
--- /dev/null
+++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx
@@ -0,0 +1,16 @@
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenuLoader = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Loader"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ // TODO Test
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ariakit/src/tableHandle/TableHandle.tsx b/packages/ariakit/src/tableHandle/TableHandle.tsx
new file mode 100644
index 0000000000..4c798d6637
--- /dev/null
+++ b/packages/ariakit/src/tableHandle/TableHandle.tsx
@@ -0,0 +1,36 @@
+import * as Ariakit from "@ariakit/react";
+
+import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const TableHandle = forwardRef<
+ HTMLButtonElement,
+ ComponentProps["TableHandle"]["Root"]
+>((props, ref) => {
+ const {
+ className,
+ children,
+ draggable,
+ onDragStart,
+ onDragEnd,
+ style,
+ ...rest
+ } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ariakit/src/toolbar/Toolbar.tsx b/packages/ariakit/src/toolbar/Toolbar.tsx
index 203d8ae3fb..c47874b0c9 100644
--- a/packages/ariakit/src/toolbar/Toolbar.tsx
+++ b/packages/ariakit/src/toolbar/Toolbar.tsx
@@ -1,23 +1,24 @@
import * as Ariakit from "@ariakit/react";
import { mergeCSSClasses } from "@blocknote/core";
-import { forwardRef, HTMLAttributes } from "react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const Toolbar = forwardRef<
- HTMLDivElement,
- HTMLAttributes
->((props, ref) => {
- const { className, children, ...rest } = props;
+type ToolbarProps = ComponentProps["FormattingToolbar"]["Root"] &
+ ComponentProps["LinkToolbar"]["Root"];
- return (
-
- {children}
-
- );
-});
+export const Toolbar = forwardRef(
+ (props, ref) => {
+ const { className, children, onMouseEnter, onMouseLeave } = props;
-// export const ToolbarButton = forwardRef(
-//
+ return (
+
+ {children}
+
+ );
+ }
+);
diff --git a/packages/ariakit/src/toolbar/ToolbarButton.tsx b/packages/ariakit/src/toolbar/ToolbarButton.tsx
index c5405d5e72..12e03320ff 100644
--- a/packages/ariakit/src/toolbar/ToolbarButton.tsx
+++ b/packages/ariakit/src/toolbar/ToolbarButton.tsx
@@ -1,18 +1,35 @@
import * as Ariakit from "@ariakit/react";
-import { isSafari } from "@blocknote/core";
-import { ToolbarButtonProps } from "@blocknote/react";
+import { isSafari, mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
import { forwardRef } from "react";
+type ToolbarButtonProps = ComponentProps["FormattingToolbar"]["Button"] &
+ ComponentProps["LinkToolbar"]["Button"];
+
/**
* Helper for basic buttons that show in the formatting toolbar.
*/
// TODO: implement tooltip
export const ToolbarButton = forwardRef(
(props, ref) => {
+ const {
+ className,
+ children,
+ // mainTooltip,
+ // secondaryTooltip,
+ icon,
+ isSelected,
+ isDisabled,
+ onClick,
+ } = props;
+
return (
{
@@ -20,52 +37,52 @@ export const ToolbarButton = forwardRef(
(e.currentTarget as HTMLButtonElement).focus();
}
}}
- onClick={props.onClick}
- data-selected={props.isSelected ? "true" : undefined}
+ onClick={onClick}
+ data-selected={isSelected ? "true" : undefined}
data-test={
props.mainTooltip.slice(0, 1).toLowerCase() +
props.mainTooltip.replace(/\s+/g, "").slice(1)
}
// size={"xs"}
- disabled={props.isDisabled || false}
+ disabled={isDisabled || false}
ref={ref}>
- {props.icon}
- {props.children}
+ {icon}
+ {children}
);
- return (
-
- {
- if (isSafari()) {
- (e.currentTarget as HTMLButtonElement).focus();
- }
- }}
- onClick={props.onClick}
- data-selected={props.isSelected ? "true" : undefined}
- data-test={
- props.mainTooltip.slice(0, 1).toLowerCase() +
- props.mainTooltip.replace(/\s+/g, "").slice(1)
- }
- // size={"xs"}
- disabled={props.isDisabled || false}
- ref={ref}>
- }>
- {props.icon}
- {props.children}
-
-
- {props.mainTooltip}
- {props.secondaryTooltip && {props.secondaryTooltip}
}
-
-
- );
+ // return (
+ //
+ // {
+ // if (isSafari()) {
+ // (e.currentTarget as HTMLButtonElement).focus();
+ // }
+ // }}
+ // onClick={props.onClick}
+ // data-selected={props.isSelected ? "true" : undefined}
+ // data-test={
+ // props.mainTooltip.slice(0, 1).toLowerCase() +
+ // props.mainTooltip.replace(/\s+/g, "").slice(1)
+ // }
+ // // size={"xs"}
+ // disabled={props.isDisabled || false}
+ // ref={ref}>
+ // }>
+ // {props.icon}
+ // {props.children}
+ //
+ //
+ // {props.mainTooltip}
+ // {props.secondaryTooltip && {props.secondaryTooltip}
}
+ //
+ //
+ // );
}
);
diff --git a/packages/ariakit/src/toolbar/ToolbarSelect.tsx b/packages/ariakit/src/toolbar/ToolbarSelect.tsx
index 40b09338a3..930a281380 100644
--- a/packages/ariakit/src/toolbar/ToolbarSelect.tsx
+++ b/packages/ariakit/src/toolbar/ToolbarSelect.tsx
@@ -1,33 +1,45 @@
import * as Ariakit from "@ariakit/react";
-import { ToolbarSelectProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { mergeCSSClasses } from "@blocknote/core";
+import { forwardRef } from "react";
+
+export const ToolbarSelect = forwardRef<
+ HTMLDivElement,
+ ComponentProps["FormattingToolbar"]["Select"]
+>((props, ref) => {
+ const { className, items, isDisabled } = props;
-export function ToolbarSelect(props: ToolbarSelectProps) {
const selectedItem = props.items.filter((p) => p.isSelected)[0];
const setValue = (value: string) => {
- props.items.find((item) => item.text === value)!.onClick?.();
+ items.find((item) => item.text === value)!.onClick?.();
};
return (
}>
- {selectedItem.icon && } {selectedItem.text}{" "}
-
+ {selectedItem.icon} {selectedItem.text}
-
- {props.items.map((option) => (
+
+ {items.map((option) => (
- {option.icon && } {option.text}
+ value={option.text}>
+ {option.icon}
+ {option.text}
+ {option.text === selectedItem.text && }
))}
);
-}
+});
diff --git a/packages/ariakit/tsconfig.json b/packages/ariakit/tsconfig.json
index 74774570bc..607ad93cfa 100644
--- a/packages/ariakit/tsconfig.json
+++ b/packages/ariakit/tsconfig.json
@@ -19,9 +19,6 @@
"declarationDir": "types",
"composite": true,
"skipLibCheck": true,
- "paths": {
- "@/*": ["./src/*"]
- }
},
"include": ["src"],
"references": [
diff --git a/packages/ariakit/vite.config.ts b/packages/ariakit/vite.config.ts
index fd9ff36a06..a50e2823d2 100644
--- a/packages/ariakit/vite.config.ts
+++ b/packages/ariakit/vite.config.ts
@@ -16,11 +16,8 @@ export default defineConfig((conf) => ({
resolve: {
alias:
conf.command === "build"
- ? ({
- "@": path.resolve(__dirname, "./src"),
- } as Record)
+ ? ({} as Record)
: ({
- "@": path.resolve(__dirname, "./src"),
// load live from sources with live reload working
"@blocknote/core": path.resolve(__dirname, "../core/src/"),
"@blocknote/react": path.resolve(__dirname, "../react/src/"),
diff --git a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
index 21f8eeeb00..af1e444a99 100644
--- a/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
+++ b/packages/core/src/blocks/ImageBlockContent/ImageBlockContent.ts
@@ -76,7 +76,7 @@ export const renderImage = (
// Text for the add image button.
const addImageButtonText = document.createElement("p");
addImageButtonText.className = "bn-add-image-button-text";
- addImageButtonText.innerText = "Add Image";
+ addImageButtonText.innerText = editor.dictionary.image.add_button;
// Wrapper element for the image, resize handles and caption.
const imageAndCaptionWrapper = document.createElement("div");
diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts
index 001782123b..9a12f039b4 100644
--- a/packages/core/src/editor/BlockNoteEditor.ts
+++ b/packages/core/src/editor/BlockNoteEditor.ts
@@ -27,10 +27,10 @@ import {
PartialBlock,
} from "../blocks/defaultBlocks";
import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin";
+import { ImagePanelProsemirrorPlugin } from "../extensions/ImagePanel/ImageToolbarPlugin";
import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin";
import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin";
import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin";
-import { ImagePanelProsemirrorPlugin } from "../extensions/ImagePanel/ImageToolbarPlugin";
import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin";
import { UniqueID } from "../extensions/UniqueID/UniqueID";
import {
@@ -62,6 +62,8 @@ import {
} from "./BlockNoteTipTapEditor";
// CSS
+import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin";
+import { en } from "../i18n/locales";
import "./Block.css";
import "./editor.css";
@@ -150,6 +152,8 @@ export class BlockNoteEditor<
contentComponent: any;
};
public blockCache = new WeakMap>();
+ public readonly dictionary = en;
+
public readonly schema: BlockNoteSchema;
public readonly blockImplementations: BlockSpecs;
@@ -221,6 +225,10 @@ export class BlockNoteEditor<
defaultStyles: true,
schema: options.schema || BlockNoteSchema.create(),
...options,
+ placeholders: {
+ ...this.dictionary.placeholders,
+ ...options.placeholders,
+ },
};
// @ts-ignore
@@ -243,7 +251,6 @@ export class BlockNoteEditor<
const extensions = getBlockNoteExtensions({
editor: this,
- placeholders: newOptions.placeholders,
domAttributes: newOptions.domAttributes || {},
blockSchema: this.schema.blockSchema,
blockSpecs: this.schema.blockSpecs,
@@ -263,6 +270,7 @@ export class BlockNoteEditor<
this.suggestionMenus.plugin,
...(this.imagePanel ? [this.imagePanel.plugin] : []),
...(this.tableHandles ? [this.tableHandles.plugin] : []),
+ PlaceholderPlugin(this, newOptions.placeholders),
];
},
});
diff --git a/packages/core/src/editor/BlockNoteExtensions.ts b/packages/core/src/editor/BlockNoteExtensions.ts
index 2cf41f98f8..a408c00f3e 100644
--- a/packages/core/src/editor/BlockNoteExtensions.ts
+++ b/packages/core/src/editor/BlockNoteExtensions.ts
@@ -14,7 +14,6 @@ import * as Y from "yjs";
import { createCopyToClipboardExtension } from "../api/exporters/copyExtension";
import { createPasteFromClipboardExtension } from "../api/parsers/pasteExtension";
import { BackgroundColorExtension } from "../extensions/BackgroundColor/BackgroundColorExtension";
-import { Placeholder } from "../extensions/Placeholder/PlaceholderExtension";
import { TextAlignmentExtension } from "../extensions/TextAlignment/TextAlignmentExtension";
import { TextColorExtension } from "../extensions/TextColor/TextColorExtension";
import { TrailingNode } from "../extensions/TrailingNode/TrailingNodeExtension";
@@ -39,7 +38,6 @@ export const getBlockNoteExtensions = <
S extends StyleSchema
>(opts: {
editor: BlockNoteEditor;
- placeholders?: Record;
domAttributes: Partial;
blockSchema: BSchema;
blockSpecs: BlockSpecs;
@@ -66,12 +64,6 @@ export const getBlockNoteExtensions = <
Gapcursor,
// DropCursor,
- Placeholder.configure({
- // TODO: This shorthand is kind of ugly
- ...(opts.placeholders !== undefined
- ? { placeholders: opts.placeholders }
- : {}),
- }),
UniqueID.configure({
types: ["blockContainer"],
}),
diff --git a/packages/core/src/extensions/Placeholder/PlaceholderExtension.ts b/packages/core/src/extensions/Placeholder/PlaceholderExtension.ts
deleted file mode 100644
index b25e6d4c5a..0000000000
--- a/packages/core/src/extensions/Placeholder/PlaceholderExtension.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { Extension } from "@tiptap/core";
-import { Plugin, PluginKey } from "prosemirror-state";
-import { Decoration, DecorationSet } from "prosemirror-view";
-
-const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
-
-/**
- * This is a modified version of the tiptap
- * placeholder plugin, that also sets hasAnchorClass
- *
- * It does not set a data-placeholder (text is currently done in css)
- *
- */
-export interface PlaceholderOptions {
- placeholders: Record;
-}
-
-export const Placeholder = Extension.create({
- name: "placeholder",
-
- addOptions() {
- return {
- placeholders: {
- default: "Enter text or type '/' for commands",
- heading: "Heading",
- bulletListItem: "List",
- numberedListItem: "List",
- },
- };
- },
-
- addProseMirrorPlugins() {
- const placeholders = this.options.placeholders;
- return [
- new Plugin({
- key: PLUGIN_KEY,
- view: () => {
- const styleEl = document.createElement("style");
- document.head.appendChild(styleEl);
- const styleSheet = styleEl.sheet!;
-
- const getBaseSelector = (additionalSelectors = "") =>
- `.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak):before`;
-
- const getSelector = (
- blockType: string | "default",
- mustBeFocused = true
- ) => {
- const mustBeFocusedSelector = mustBeFocused
- ? `[data-is-empty-and-focused]`
- : ``;
-
- if (blockType === "default") {
- return getBaseSelector(mustBeFocusedSelector);
- }
-
- const blockTypeSelector = `[data-content-type="${blockType}"]`;
- return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
- };
-
- for (const [blockType, placeholder] of Object.entries(placeholders)) {
- const mustBeFocused = blockType === "default";
-
- styleSheet.insertRule(
- `${getSelector(
- blockType,
- mustBeFocused
- )}{ content: ${JSON.stringify(placeholder)}; }`
- );
-
- // For some reason, the placeholders which show when the block is focused
- // take priority over ones which show depending on block type, so we need
- // to make sure the block specific ones are also used when the block is
- // focused.
- if (!mustBeFocused) {
- styleSheet.insertRule(
- `${getSelector(blockType, true)}{ content: ${JSON.stringify(
- placeholder
- )}; }`
- );
- }
- }
-
- return {
- destroy: () => {
- document.head.removeChild(styleEl);
- },
- };
- },
- props: {
- // TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
- decorations: (state) => {
- const { doc, selection } = state;
-
- const active = this.editor.isEditable;
-
- if (!active) {
- return;
- }
-
- if (!selection.empty) {
- return;
- }
-
- const $pos = selection.$anchor;
- const node = $pos.parent;
-
- if (node.content.size > 0) {
- return null;
- }
-
- const before = $pos.before();
-
- const dec = Decoration.node(before, before + node.nodeSize, {
- "data-is-empty-and-focused": "true",
- });
-
- return DecorationSet.create(doc, [dec]);
- },
- },
- }),
- ];
- },
-});
diff --git a/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts
new file mode 100644
index 0000000000..aa7f423126
--- /dev/null
+++ b/packages/core/src/extensions/Placeholder/PlaceholderPlugin.ts
@@ -0,0 +1,95 @@
+import { Plugin, PluginKey } from "prosemirror-state";
+import { Decoration, DecorationSet } from "prosemirror-view";
+import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
+
+const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`);
+
+export const PlaceholderPlugin = (
+ editor: BlockNoteEditor,
+ placeholders: Record
+) => {
+ return new Plugin({
+ key: PLUGIN_KEY,
+ view: () => {
+ const styleEl = document.createElement("style");
+ document.head.appendChild(styleEl);
+ const styleSheet = styleEl.sheet!;
+
+ const getBaseSelector = (additionalSelectors = "") =>
+ `.bn-block-content${additionalSelectors} .bn-inline-content:has(> .ProseMirror-trailingBreak):before`;
+
+ const getSelector = (
+ blockType: string | "default",
+ mustBeFocused = true
+ ) => {
+ const mustBeFocusedSelector = mustBeFocused
+ ? `[data-is-empty-and-focused]`
+ : ``;
+
+ if (blockType === "default") {
+ return getBaseSelector(mustBeFocusedSelector);
+ }
+
+ const blockTypeSelector = `[data-content-type="${blockType}"]`;
+ return getBaseSelector(mustBeFocusedSelector + blockTypeSelector);
+ };
+
+ for (const [blockType, placeholder] of Object.entries(placeholders)) {
+ const mustBeFocused = blockType === "default";
+
+ styleSheet.insertRule(
+ `${getSelector(blockType, mustBeFocused)}{ content: ${JSON.stringify(
+ placeholder
+ )}; }`
+ );
+
+ // For some reason, the placeholders which show when the block is focused
+ // take priority over ones which show depending on block type, so we need
+ // to make sure the block specific ones are also used when the block is
+ // focused.
+ if (!mustBeFocused) {
+ styleSheet.insertRule(
+ `${getSelector(blockType, true)}{ content: ${JSON.stringify(
+ placeholder
+ )}; }`
+ );
+ }
+ }
+
+ return {
+ destroy: () => {
+ document.head.removeChild(styleEl);
+ },
+ };
+ },
+ props: {
+ // TODO: maybe also add placeholder for empty document ("e.g.: start writing..")
+ decorations: (state) => {
+ const { doc, selection } = state;
+
+ if (!editor.isEditable) {
+ return;
+ }
+
+ if (!selection.empty) {
+ return;
+ }
+
+ const $pos = selection.$anchor;
+ const node = $pos.parent;
+
+ if (node.content.size > 0) {
+ return null;
+ }
+
+ const before = $pos.before();
+
+ const dec = Decoration.node(before, before + node.nodeSize, {
+ "data-is-empty-and-focused": "true",
+ });
+
+ return DecorationSet.create(doc, [dec]);
+ },
+ },
+ });
+};
diff --git a/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts b/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts
index 4505a3aaa9..874c7e318c 100644
--- a/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts
+++ b/packages/core/src/extensions/SuggestionMenu/DefaultSuggestionItem.ts
@@ -1,4 +1,7 @@
+import type { Dictionary } from "../../i18n/dictionary";
+
export type DefaultSuggestionItem = {
+ key: keyof Dictionary["slash_menu"];
title: string;
onItemClick: () => void;
subtext?: string;
diff --git a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
index a56363634e..8805c6a4d6 100644
--- a/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
+++ b/packages/core/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts
@@ -83,95 +83,82 @@ export function getDefaultSlashMenuItems<
if (checkDefaultBlockTypeInSchema("heading", editor)) {
items.push(
{
- title: "Heading 1",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 1 },
});
},
- subtext: "Used for a top-level heading",
badge: formatKeyboardShortcut("Mod-Alt-1"),
- aliases: ["h", "heading1", "h1"],
- group: "Headings",
+ key: "heading",
+ ...editor.dictionary.slash_menu.heading,
},
{
- title: "Heading 2",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 2 },
});
},
- subtext: "Used for key sections",
badge: formatKeyboardShortcut("Mod-Alt-2"),
- aliases: ["h2", "heading2", "subheading"],
- group: "Headings",
+ key: "heading_2",
+ ...editor.dictionary.slash_menu.heading_2,
},
{
- title: "Heading 3",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "heading",
props: { level: 3 },
});
},
- subtext: "Used for subsections and group headings",
badge: formatKeyboardShortcut("Mod-Alt-3"),
- aliases: ["h3", "heading3", "subheading"],
- group: "Headings",
+ key: "heading_3",
+ ...editor.dictionary.slash_menu.heading_3,
}
);
}
if (checkDefaultBlockTypeInSchema("numberedListItem", editor)) {
items.push({
- title: "Numbered List",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "numberedListItem",
});
},
- subtext: "Used to display a numbered list",
badge: formatKeyboardShortcut("Mod-Shift-7"),
- aliases: ["ol", "li", "list", "numberedlist", "numbered list"],
- group: "Basic blocks",
+ key: "numbered_list",
+ ...editor.dictionary.slash_menu.numbered_list,
});
}
if (checkDefaultBlockTypeInSchema("bulletListItem", editor)) {
items.push({
- title: "Bullet List",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "bulletListItem",
});
},
- subtext: "Used to display an unordered list",
badge: formatKeyboardShortcut("Mod-Shift-8"),
- aliases: ["ul", "li", "list", "bulletlist", "bullet list"],
- group: "Basic blocks",
+ key: "bullet_list",
+ ...editor.dictionary.slash_menu.bullet_list,
});
}
if (checkDefaultBlockTypeInSchema("paragraph", editor)) {
items.push({
- title: "Paragraph",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "paragraph",
});
},
- subtext: "Used for the body of your document",
badge: formatKeyboardShortcut("Mod-Alt-0"),
- aliases: ["p", "paragraph"],
- group: "Basic blocks",
+ key: "paragraph",
+ ...editor.dictionary.slash_menu.paragraph,
});
}
if (checkDefaultBlockTypeInSchema("table", editor)) {
items.push({
- title: "Table",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "table",
@@ -188,16 +175,14 @@ export function getDefaultSlashMenuItems<
},
});
},
- subtext: "Used for for tables",
- aliases: ["table"],
- group: "Advanced",
badge: undefined,
+ key: "table",
+ ...editor.dictionary.slash_menu.table,
});
}
if (checkDefaultBlockTypeInSchema("image", editor)) {
items.push({
- title: "Image",
onItemClick: () => {
const insertedBlock = insertOrUpdateBlock(editor, {
type: "image",
@@ -210,19 +195,8 @@ export function getDefaultSlashMenuItems<
})
);
},
- subtext: "Insert an image",
- aliases: [
- "image",
- "imageUpload",
- "upload",
- "img",
- "picture",
- "media",
- "url",
- "drive",
- "dropbox",
- ],
- group: "Media",
+ key: "image",
+ ...editor.dictionary.slash_menu.image,
});
}
diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts
index 8b1918d8e5..ac0be6cda9 100644
--- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts
+++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts
@@ -350,7 +350,7 @@ export class TableHandlesView<
};
destroy() {
- this.pmView.dom.removeEventListener("mousedown", this.mouseMoveHandler);
+ this.pmView.dom.removeEventListener("mousemove", this.mouseMoveHandler);
document.removeEventListener("dragover", this.dragOverHandler);
document.removeEventListener("drop", this.dropHandler);
diff --git a/packages/core/src/i18n/dictionary.ts b/packages/core/src/i18n/dictionary.ts
new file mode 100644
index 0000000000..c93e6533c5
--- /dev/null
+++ b/packages/core/src/i18n/dictionary.ts
@@ -0,0 +1,17 @@
+// function scramble(dict: any) {
+// const newDict: any = {} as any;
+
+import type { en } from "./locales";
+
+// for (const key in dict) {
+// if (typeof dict[key] === "object") {
+// newDict[key] = scramble(dict[key]);
+// } else {
+// newDict[key] = dict[key].split("").reverse().join("");
+// }
+// }
+
+// return newDict;
+// }
+
+export type Dictionary = typeof en;
diff --git a/packages/core/src/i18n/locales/en.ts b/packages/core/src/i18n/locales/en.ts
new file mode 100644
index 0000000000..3efd049612
--- /dev/null
+++ b/packages/core/src/i18n/locales/en.ts
@@ -0,0 +1,192 @@
+export const en = {
+ slash_menu: {
+ heading: {
+ title: "Heading 1",
+ subtext: "Used for a top-level heading",
+ aliases: ["h", "heading1", "h1"],
+ group: "Headings",
+ },
+ heading_2: {
+ title: "Heading 2",
+ subtext: "Used for key sections",
+ aliases: ["h2", "heading2", "subheading"],
+ group: "Headings",
+ },
+ heading_3: {
+ title: "Heading 3",
+ subtext: "Used for subsections and group headings",
+ aliases: ["h3", "heading3", "subheading"],
+ group: "Headings",
+ },
+ numbered_list: {
+ title: "Numbered List",
+ subtext: "Used to display a numbered list",
+ aliases: ["ol", "li", "list", "numberedlist", "numbered list"],
+ group: "Basic blocks",
+ },
+ bullet_list: {
+ title: "Bullet List",
+ subtext: "Used to display an unordered list",
+ aliases: ["ul", "li", "list", "bulletlist", "bullet list"],
+ group: "Basic blocks",
+ },
+ paragraph: {
+ title: "Paragraph",
+ subtext: "Used for the body of your document",
+ aliases: ["p", "paragraph"],
+ group: "Basic blocks",
+ },
+ table: {
+ title: "Table",
+ subtext: "Used for for tables",
+ aliases: ["table"],
+ group: "Advanced",
+ },
+ image: {
+ title: "Image",
+ subtext: "Insert an image",
+ aliases: [
+ "image",
+ "imageUpload",
+ "upload",
+ "img",
+ "picture",
+ "media",
+ "url",
+ "drive",
+ "dropbox",
+ ],
+ group: "Media",
+ },
+ },
+ placeholders: {
+ default: "Enter text or type '/' for commands",
+ heading: "Heading",
+ bulletListItem: "List",
+ numberedListItem: "List",
+ },
+ image: {
+ add_button: "Add Image",
+ },
+ // from react package:
+ drag_handle: {
+ delete_menuitem: "Delete",
+ colors_menuitem: "Colors",
+ },
+ table_handle: {
+ delete_column_menuitem: "Delete column",
+ delete_row_menuitem: "Delete row",
+ add_left_menuitem: "Add column left",
+ add_right_menuitem: "Add column right",
+ add_above_menuitem: "Add row above",
+ add_below_menuitem: "Add row below",
+ },
+ suggestion_menu: {
+ no_items_title: "No items found",
+ loading: "Loading…",
+ },
+ color_picker: {
+ text_title: "Text",
+ background_title: "Background",
+ colors: {
+ default: "Default",
+ gray: "Gray",
+ brown: "Brown",
+ red: "Red",
+ orange: "Orange",
+ yellow: "Yellow",
+ green: "Green",
+ blue: "Blue",
+ purple: "Purple",
+ pink: "Pink",
+ },
+ },
+
+ formatting_toolbar: {
+ bold: {
+ tooltip: "Bold",
+ secondary_tooltip: "Mod+B",
+ },
+ italic: {
+ tooltip: "Italic",
+ secondary_tooltip: "Mod+I",
+ },
+ underline: {
+ tooltip: "Underline",
+ secondary_tooltip: "Mod+U",
+ },
+ strike: {
+ tooltip: "Strike",
+ secondary_tooltip: "Mod+Shift+X",
+ },
+ code: {
+ tooltip: "Code",
+ secondary_tooltip: "",
+ },
+ colors: {
+ tooltip: "Colors",
+ },
+ link: {
+ tooltip: "Create link",
+ secondary_tooltip: "Mod+K",
+ },
+ image_caption: {
+ tooltip: "Edit caption",
+ input_placeholder: "Edit caption",
+ },
+ image_replace: {
+ tooltip: "Replace image",
+ },
+ nest: {
+ tooltip: "Nest block",
+ secondary_tooltip: "Tab",
+ },
+ unnest: {
+ tooltip: "Unnest block",
+ secondary_tooltip: "Shift+Tab",
+ },
+ align_left: {
+ tooltip: "Align text left",
+ },
+ align_center: {
+ tooltip: "Align text center",
+ },
+ align_right: {
+ tooltip: "Align text right",
+ },
+ align_justify: {
+ tooltip: "Justify text",
+ },
+ },
+ image_panel: {
+ upload: {
+ title: "Upload",
+ file_placeholder: "Upload image",
+ upload_error: "Error: Upload failed",
+ },
+ embed: {
+ title: "Embed",
+ embed_button: "Embed image",
+ url_placeholder: "Enter URL",
+ },
+ },
+ link_toolbar: {
+ delete: {
+ tooltip: "Remove link",
+ },
+ edit: {
+ text: "Edit link",
+ tooltip: "Edit",
+ },
+ open: {
+ tooltip: "Open in new tab",
+ },
+ form: {
+ title_placeholder: "Edit title",
+ url_placeholder: "Edit URL",
+ },
+ },
+ generic: {
+ ctrl_shortcut: "Ctrl",
+ },
+};
diff --git a/packages/core/src/i18n/locales/index.ts b/packages/core/src/i18n/locales/index.ts
new file mode 100644
index 0000000000..fe36955d21
--- /dev/null
+++ b/packages/core/src/i18n/locales/index.ts
@@ -0,0 +1 @@
+export * from "./en";
diff --git a/packages/core/src/i18n/locales/nl.ts b/packages/core/src/i18n/locales/nl.ts
new file mode 100644
index 0000000000..be0cf48363
--- /dev/null
+++ b/packages/core/src/i18n/locales/nl.ts
@@ -0,0 +1,193 @@
+import type { Dictionary } from "../dictionary";
+
+export const nl: Dictionary = {
+ slash_menu: {
+ heading: {
+ title: "Kop 1",
+ subtext: "Gebruikt voor een hoofdkop",
+ aliases: ["h", "kop1", "h1"],
+ group: "Koppen",
+ },
+ heading_2: {
+ title: "Kop 2",
+ subtext: "Gebruikt voor belangrijke secties",
+ aliases: ["h2", "kop2", "subkop"],
+ group: "Koppen",
+ },
+ heading_3: {
+ title: "Kop 3",
+ subtext: "Gebruikt voor subsecties en groepskoppen",
+ aliases: ["h3", "kop3", "subkop"],
+ group: "Koppen",
+ },
+ numbered_list: {
+ title: "Genummerde Lijst",
+ subtext: "Gebruikt om een genummerde lijst weer te geven",
+ aliases: ["ol", "li", "lijst", "genummerdelijst", "genummerde lijst"],
+ group: "Basisblokken",
+ },
+ bullet_list: {
+ title: "Puntenlijst",
+ subtext: "Gebruikt om een ongeordende lijst weer te geven",
+ aliases: ["ul", "li", "lijst", "puntenlijst", "punten lijst"],
+ group: "Basisblokken",
+ },
+ paragraph: {
+ title: "Paragraaf",
+ subtext: "Gebruikt voor de hoofdtekst van uw document",
+ aliases: ["p", "paragraaf"],
+ group: "Basisblokken",
+ },
+ table: {
+ title: "Tabel",
+ subtext: "Gebruikt voor tabellen",
+ aliases: ["tabel"],
+ group: "Geavanceerd",
+ },
+ image: {
+ title: "Afbeelding",
+ subtext: "Voeg een afbeelding in",
+ aliases: [
+ "afbeelding",
+ "imageUpload",
+ "upload",
+ "img",
+ "foto",
+ "media",
+ "url",
+ "drive",
+ "dropbox",
+ ],
+ group: "Media",
+ },
+ },
+ placeholders: {
+ default: "Voer tekst in of type '/' voor commando's",
+ heading: "Kop",
+ bulletListItem: "Lijst",
+ numberedListItem: "Lijst",
+ },
+ image: {
+ add_button: "Afbeelding toevoegen",
+ },
+ // from react package:
+ drag_handle: {
+ delete_menuitem: "Verwijder",
+ colors_menuitem: "Kleuren",
+ },
+ table_handle: {
+ delete_column_menuitem: "Verwijder kolom",
+ delete_row_menuitem: "Verwijder rij",
+ add_left_menuitem: "Voeg kolom links toe",
+ add_right_menuitem: "Voeg kolom rechts toe",
+ add_above_menuitem: "Voeg rij boven toe",
+ add_below_menuitem: "Voeg rij onder toe",
+ },
+ suggestion_menu: {
+ no_items_title: "Geen items gevonden",
+ loading: "Laden…",
+ },
+ color_picker: {
+ text_title: "Tekst",
+ background_title: "Achtergrond",
+ colors: {
+ default: "Standaard",
+ gray: "Grijs",
+ brown: "Bruin",
+ red: "Rood",
+ orange: "Oranje",
+ yellow: "Geel",
+ green: "Groen",
+ blue: "Blauw",
+ purple: "Paars",
+ pink: "Roze",
+ },
+ },
+ formatting_toolbar: {
+ bold: {
+ tooltip: "Vet",
+ secondary_tooltip: "Mod+B",
+ },
+ italic: {
+ tooltip: "Cursief",
+ secondary_tooltip: "Mod+I",
+ },
+ underline: {
+ tooltip: "Onderstrepen",
+ secondary_tooltip: "Mod+U",
+ },
+ strike: {
+ tooltip: "Doorstrepen",
+ secondary_tooltip: "Mod+Shift+X",
+ },
+ code: {
+ tooltip: "Code",
+ secondary_tooltip: "",
+ },
+ colors: {
+ tooltip: "Kleuren",
+ },
+ link: {
+ tooltip: "Maak link",
+ secondary_tooltip: "Mod+K",
+ },
+ image_caption: {
+ tooltip: "Bewerk onderschrift",
+ input_placeholder: "Bewerk onderschrift",
+ },
+ image_replace: {
+ tooltip: "Vervang afbeelding",
+ },
+ nest: {
+ tooltip: "Nest blok",
+ secondary_tooltip: "Tab",
+ },
+ unnest: {
+ tooltip: "Ontnest blok",
+ secondary_tooltip: "Shift+Tab",
+ },
+ align_left: {
+ tooltip: "Tekst links uitlijnen",
+ },
+ align_center: {
+ tooltip: "Tekst centreren",
+ },
+ align_right: {
+ tooltip: "Tekst rechts uitlijnen",
+ },
+ align_justify: {
+ tooltip: "Tekst uitvullen",
+ },
+ },
+ image_panel: {
+ upload: {
+ title: "Upload",
+ file_placeholder: "Upload afbeelding",
+ upload_error: "Fout: Upload mislukt",
+ },
+ embed: {
+ title: "Insluiten",
+ embed_button: "Insluiten afbeelding",
+ url_placeholder: "Voer URL in",
+ },
+ },
+ link_toolbar: {
+ delete: {
+ tooltip: "Verwijder link",
+ },
+ edit: {
+ text: "Bewerk link",
+ tooltip: "Bewerk",
+ },
+ open: {
+ tooltip: "Open in nieuw tabblad",
+ },
+ form: {
+ title_placeholder: "Bewerk titel",
+ url_placeholder: "Bewerk URL",
+ },
+ },
+ generic: {
+ ctrl_shortcut: "Ctrl",
+ },
+};
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 5a202f72bd..1a70105d11 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -1,3 +1,4 @@
+import * as locales from "./i18n/locales";
export * from "./api/exporters/html/externalHTMLExporter";
export * from "./api/exporters/html/internalHTMLSerializer";
export * from "./api/testUtil";
@@ -25,4 +26,6 @@ export * from "./util/string";
export * from "./api/nodeConversions/nodeConversions";
export * from "./api/testUtil/partialBlockTestUtil";
export * from "./extensions/UniqueID/UniqueID";
+export * from "./i18n/dictionary";
export { UnreachableCaseError } from "./util/typescript";
+export { locales };
diff --git a/packages/core/src/util/browser.ts b/packages/core/src/util/browser.ts
index e5fcf8ac71..9ecdf3250d 100644
--- a/packages/core/src/util/browser.ts
+++ b/packages/core/src/util/browser.ts
@@ -4,11 +4,11 @@ export const isAppleOS = () =>
(/AppleWebKit/.test(navigator.userAgent) &&
/Mobile\/\w+/.test(navigator.userAgent)));
-export function formatKeyboardShortcut(shortcut: string) {
+export function formatKeyboardShortcut(shortcut: string, ctrlText = "Ctrl") {
if (isAppleOS()) {
return shortcut.replace("Mod", "⌘");
} else {
- return shortcut.replace("Mod", "Ctrl");
+ return shortcut.replace("Mod", ctrlText);
}
}
diff --git a/packages/dev-scripts/examples/template-react/package.json.template.tsx b/packages/dev-scripts/examples/template-react/package.json.template.tsx
index 5b391fab85..aa21a45bbc 100644
--- a/packages/dev-scripts/examples/template-react/package.json.template.tsx
+++ b/packages/dev-scripts/examples/template-react/package.json.template.tsx
@@ -13,8 +13,11 @@ const template = (project: Project) => ({
lint: "eslint . --max-warnings 0",
},
dependencies: {
- "@blocknote/core": "^0.12.0",
- "@blocknote/react": "^0.12.0",
+ "@blocknote/core": "^0.12.1",
+ "@blocknote/react": "^0.12.2",
+ "@blocknote/ariakit": "^0.12.2",
+ "@blocknote/mantine": "^0.12.2",
+ "@blocknote/shadcn": "^0.12.2",
react: "^18.2.0",
"react-dom": "^18.2.0",
...(project.config?.dependencies || {}),
diff --git a/packages/mantine/package.json b/packages/mantine/package.json
index 6d9b7c7dbc..f656fbff26 100644
--- a/packages/mantine/package.json
+++ b/packages/mantine/package.json
@@ -45,8 +45,6 @@
"build-bundled": "tsc && vite build --config vite.config.bundled.ts && git checkout tmp-releases && rm -rf ../../release && mv ../../release-tmp ../../release",
"preview": "vite preview",
"lint": "eslint src --max-warnings 0",
- "test": "vitest --run",
- "test:watch": "vitest --watch",
"clean": "rimraf dist && rimraf types"
},
"dependencies": {
@@ -56,7 +54,8 @@
"@mantine/hooks": "^7.5.0",
"@mantine/utils": "^6.0.21",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "react-icons": "^4.3.1"
},
"devDependencies": {
"@types/react": "^18.0.25",
@@ -69,8 +68,7 @@
"typescript": "^5.3.3",
"vite": "^4.4.8",
"vite-plugin-eslint": "^1.8.1",
- "vite-plugin-externalize-deps": "^0.7.0",
- "vitest": "^0.34.1"
+ "vite-plugin-externalize-deps": "^0.7.0"
},
"peerDependencies": {
"react": "^18",
diff --git a/packages/mantine/src/form/TextInput.tsx b/packages/mantine/src/form/TextInput.tsx
new file mode 100644
index 0000000000..db4a76a1c6
--- /dev/null
+++ b/packages/mantine/src/form/TextInput.tsx
@@ -0,0 +1,39 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const TextInput = forwardRef<
+ HTMLInputElement,
+ ComponentProps["Generic"]["Form"]["TextInput"]
+>((props, ref) => {
+ const {
+ className,
+ name,
+ label,
+ icon,
+ value,
+ autoFocus,
+ placeholder,
+ onKeyDown,
+ onChange,
+ onSubmit,
+ } = props;
+
+ return (
+
+ );
+});
diff --git a/packages/mantine/src/index.tsx b/packages/mantine/src/index.tsx
index 76a4bd4abc..285b27397a 100644
--- a/packages/mantine/src/index.tsx
+++ b/packages/mantine/src/index.tsx
@@ -1,11 +1,12 @@
-import { Loader, PopoverDropdown, PopoverTarget } from "@mantine/core";
import {
BlockNoteViewRaw,
+ Components,
ComponentsContext,
- ComponentsContextValue,
} from "@blocknote/react";
+import { MantineProvider } from "@mantine/core";
import { ComponentProps } from "react";
-import { TextInput } from "./input/TextInput";
+
+import { TextInput } from "./form/TextInput";
import {
Menu,
MenuDivider,
@@ -14,61 +15,96 @@ import {
MenuLabel,
MenuTrigger,
} from "./menu/Menu";
-import { Popover } from "./popover/Popover";
-import { Toolbar } from "./toolbar/Toolbar";
-import { ToolbarButton } from "./toolbar/ToolbarButton";
-import { ToolbarSelect } from "./toolbar/ToolbarSelect";
import { Panel } from "./panel/Panel";
import { PanelButton } from "./panel/PanelButton";
import { PanelFileInput } from "./panel/PanelFileInput";
import { PanelTab } from "./panel/PanelTab";
import { PanelTextInput } from "./panel/PanelTextInput";
+import { Popover, PopoverContent, PopoverTrigger } from "./popover/Popover";
+import { SideMenu } from "./sideMenu/SideMenu";
+import { SideMenuButton } from "./sideMenu/SideMenuButton";
+import { SuggestionMenu } from "./suggestionMenu/SuggestionMenu";
+import { SuggestionMenuItem } from "./suggestionMenu/SuggestionMenuItem";
+import { SuggestionMenuEmptyItem } from "./suggestionMenu/SuggestionMenuEmptyItem";
+import { SuggestionMenuLabel } from "./suggestionMenu/SuggestionMenuLabel";
+import { SuggestionMenuLoader } from "./suggestionMenu/SuggestionMenuLoader";
+import { TableHandle } from "./tableHandle/TableHandle";
+import { Toolbar } from "./toolbar/Toolbar";
+import { ToolbarButton } from "./toolbar/ToolbarButton";
+import { ToolbarSelect } from "./toolbar/ToolbarSelect";
-export const components: ComponentsContextValue = {
- Toolbar,
- ToolbarButton,
- ToolbarSelect,
- Menu,
- MenuTrigger,
- MenuDropdown,
- MenuDivider,
- MenuLabel,
- MenuItem,
- Panel,
- PanelButton,
- PanelFileInput,
- PanelTab,
- PanelTextInput,
- Popover: Popover,
- PopoverContent: PopoverDropdown,
- PopoverTrigger: PopoverTarget,
- TextInput,
- Form: (props) => ,
- SuggestionMenuLoader: () => (
-
- ),
+import "./style.css";
+
+export const components: Components = {
+ FormattingToolbar: {
+ Root: Toolbar,
+ Button: ToolbarButton,
+ Select: ToolbarSelect,
+ },
+ ImagePanel: {
+ Root: Panel,
+ Button: PanelButton,
+ FileInput: PanelFileInput,
+ TabPanel: PanelTab,
+ TextInput: PanelTextInput,
+ },
+ LinkToolbar: {
+ Root: Toolbar,
+ Button: ToolbarButton,
+ },
+ SideMenu: {
+ Root: SideMenu,
+ Button: SideMenuButton,
+ },
+ SuggestionMenu: {
+ Root: SuggestionMenu,
+ Item: SuggestionMenuItem,
+ EmptyItem: SuggestionMenuEmptyItem,
+ Label: SuggestionMenuLabel,
+ Loader: SuggestionMenuLoader,
+ },
+ TableHandle: {
+ Root: TableHandle,
+ },
+ Generic: {
+ Form: {
+ Root: (props) => {props.children}
,
+ TextInput: TextInput,
+ },
+ Menu: {
+ Root: Menu,
+ Trigger: MenuTrigger,
+ Dropdown: MenuDropdown,
+ Divider: MenuDivider,
+ Label: MenuLabel,
+ Item: MenuItem,
+ },
+ Popover: {
+ Root: Popover,
+ Trigger: PopoverTrigger,
+ Content: PopoverContent,
+ },
+ },
+};
+
+const mantineTheme = {
+ // Removes button press effect
+ activeClassName: "",
};
export const BlockNoteView = (
- props: ComponentProps
+ // TODO: Fix typing
+ props: ComponentProps>
) => {
return (
-
+ {/* `cssVariablesSelector` scopes Mantine CSS variables to only the editor, */}
+ {/* as proposed here: https://github.com/orgs/mantinedev/discussions/5685 */}
+
+
+
);
};
-
-/*
-
-TODO:
-- allow passing in shadcn components
-- fix other libs
-- select items
-- menu colors
-- caret
-- suggestion menu?
-- forms
-- toggle buttons
-- zindex
-*/
diff --git a/packages/mantine/src/input/TextInput.tsx b/packages/mantine/src/input/TextInput.tsx
deleted file mode 100644
index 22501d210d..0000000000
--- a/packages/mantine/src/input/TextInput.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as Mantine from "@mantine/core";
-
-import { TextInputProps } from "@blocknote/react";
-
-export const TextInput = (props: TextInputProps) => {
- const { icon, ...rest } = props;
- return ;
-};
diff --git a/packages/react/src/editor/mantineStyles.css b/packages/mantine/src/mantineStyles.css
similarity index 100%
rename from packages/react/src/editor/mantineStyles.css
rename to packages/mantine/src/mantineStyles.css
diff --git a/packages/mantine/src/menu/Menu.tsx b/packages/mantine/src/menu/Menu.tsx
index d92cf4201b..16d14aeea6 100644
--- a/packages/mantine/src/menu/Menu.tsx
+++ b/packages/mantine/src/menu/Menu.tsx
@@ -1,11 +1,6 @@
import * as Mantine from "@mantine/core";
-import {
- MenuDropdownProps,
- MenuItemProps,
- MenuProps,
- MenuTriggerProps,
-} from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
import {
createContext,
forwardRef,
@@ -26,9 +21,61 @@ const SubMenuContext = createContext<
| undefined
>(undefined);
-const SubMenu = (props: MenuProps) => {
- const { sub, onOpenChange, open, defaultOpen, ...rest } = props;
-
+// https://github.com/orgs/mantinedev/discussions/2307
+// Mantine does not officially support sub menus, so we have to use a workaround
+// which uses an unconventional nesting structure:
+//
+// Conventional nesting structure (used by Ariakit/ShadCN):
+//
+//
+// Required structure for Mantine:
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+const SubMenu = forwardRef<
+ HTMLDivElement,
+ ComponentProps["Generic"]["Menu"]["Root"]
+>((props, ref) => {
+ const {
+ children,
+ onOpenChange,
+ position,
+ // sub
+ } = props;
const [opened, setOpened] = useState(false);
const menuCloseTimer = useRef | undefined>();
@@ -58,23 +105,29 @@ const SubMenu = (props: MenuProps) => {
onMenuMouseOver: mouseOver,
onMenuMouseLeave: mouseLeave,
}}>
- onOpenChange?.(false)}
- onOpen={() => onOpenChange?.(true)}
- defaultOpened={defaultOpen}
- closeOnItemClick={false}
- {...rest}
- position="right"
- />
+
+ onOpenChange?.(false)}
+ onOpen={() => onOpenChange?.(true)}
+ position={position}>
+ {children}
+
+
);
-};
+});
-export const Menu = (props: MenuProps) => {
- const { sub, onOpenChange, open, defaultOpen, ...rest } = props;
+export const Menu = (props: ComponentProps["Generic"]["Menu"]["Root"]) => {
+ const { children, onOpenChange, position, sub } = props;
if (sub) {
return ;
@@ -86,81 +139,105 @@ export const Menu = (props: MenuProps) => {
middlewares={{ flip: true, shift: true, inline: false, size: true }}
onClose={() => onOpenChange?.(false)}
onOpen={() => onOpenChange?.(true)}
- defaultOpened={defaultOpen}
- closeOnItemClick={false}
- {...rest}
- position="right"
- />
+ position={position}>
+ {children}
+
);
};
-export const MenuItem = forwardRef(
- (props, ref) => {
- const { icon, checked, expandArrow, subTrigger, ...rest } = props;
- const ctx = useContext(SubMenuContext);
-
- const onMouseLeave = subTrigger ? ctx!.onTriggerMouseLeave : undefined;
- const onMouseOver = subTrigger ? ctx!.onTriggerMouseOver : undefined;
+export const MenuItem = forwardRef<
+ HTMLDivElement,
+ ComponentProps["Generic"]["Menu"]["Item"]
+>((props, ref) => {
+ const { className, children, icon, checked, subTrigger, onClick, ...rest } =
+ props;
- return (
-
- {checked ? (
-
- ) : checked === false ? (
-
- ) : undefined}
- {expandArrow && }
- >
- }
- onMouseOver={onMouseOver}
- onMouseLeave={onMouseLeave}
- {...rest}
- ref={ref}
- />
- );
- }
-);
+ const ctx = useContext(SubMenuContext);
-export const MenuTrigger = forwardRef(
- (props, ref) => {
- const { sub, children, ...rest } = props;
+ if (subTrigger) {
return (
-
+
{children}
-
+
+
);
}
-);
-export const MenuDropdown = forwardRef(
- (props, ref) => {
- const { sub, ...rest } = props;
- const ctx = useContext(SubMenuContext);
+ const onMouseLeave = subTrigger ? ctx!.onTriggerMouseLeave : undefined;
+ const onMouseOver = subTrigger ? ctx!.onTriggerMouseOver : undefined;
- return (
-
- );
- }
-);
+ return (
+
+ ) : checked === false ? (
+
+ ) : null
+ }
+ onMouseOver={onMouseOver}
+ onMouseLeave={onMouseLeave}
+ onClick={onClick}
+ {...rest}>
+ {children}
+
+ );
+});
-export const MenuDivider = forwardRef>(
- (props, ref) => {
- return ;
- }
-);
+export const MenuTrigger = (
+ props: ComponentProps["Generic"]["Menu"]["Trigger"]
+) => {
+ const {
+ children,
+ // sub
+ } = props;
-export const MenuLabel = forwardRef>(
- (props, ref) => {
- return ;
- }
-);
+ return {children};
+};
+
+export const MenuDropdown = forwardRef<
+ HTMLDivElement,
+ ComponentProps["Generic"]["Menu"]["Dropdown"]
+>((props, ref) => {
+ const { className, children, sub } = props;
+
+ const ctx = useContext(SubMenuContext);
+
+ return (
+
+ {children}
+
+ );
+});
+
+export const MenuDivider = forwardRef<
+ HTMLDivElement,
+ ComponentProps["Generic"]["Menu"]["Divider"]
+>((props, ref) => {
+ const { className } = props;
+
+ return ;
+});
+
+export const MenuLabel = forwardRef<
+ HTMLDivElement,
+ ComponentProps["Generic"]["Menu"]["Label"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/mantine/src/panel/Panel.tsx b/packages/mantine/src/panel/Panel.tsx
index e99234fa9a..78a29f6231 100644
--- a/packages/mantine/src/panel/Panel.tsx
+++ b/packages/mantine/src/panel/Panel.tsx
@@ -1,22 +1,42 @@
import * as Mantine from "@mantine/core";
-import { PanelProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const Panel = forwardRef<
+ HTMLDivElement,
+ ComponentProps["ImagePanel"]["Root"]
+>((props, ref) => {
+ const {
+ className,
+ tabs,
+ defaultOpenTab,
+ openTab,
+ setOpenTab,
+ loading,
+ // setLoading,
+ } = props;
-export const Panel = (props: PanelProps) => {
return (
-
-
- {props.loading && }
+
+
+ {loading && }
- {props.tabs.map((tab) => (
-
+ {tabs.map((tab) => (
+
{tab.name}
))}
- {props.tabs.map((tab) => (
+ {tabs.map((tab) => (
{tab.tabPanel}
@@ -24,4 +44,4 @@ export const Panel = (props: PanelProps) => {
);
-};
+});
diff --git a/packages/mantine/src/panel/PanelButton.tsx b/packages/mantine/src/panel/PanelButton.tsx
index b811820dad..37909ce62a 100644
--- a/packages/mantine/src/panel/PanelButton.tsx
+++ b/packages/mantine/src/panel/PanelButton.tsx
@@ -1,13 +1,22 @@
import * as Mantine from "@mantine/core";
-import { PanelButtonProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const PanelButton = (props: PanelButtonProps) => {
- const { children, ...rest } = props;
+export const PanelButton = forwardRef<
+ HTMLButtonElement,
+ ComponentProps["ImagePanel"]["Button"]
+>((props, ref) => {
+ const { className, children, onClick, ...rest } = props;
return (
-
+
{children}
);
-};
+});
diff --git a/packages/mantine/src/panel/PanelFileInput.tsx b/packages/mantine/src/panel/PanelFileInput.tsx
index 8efdddd8f4..5c38c138a4 100644
--- a/packages/mantine/src/panel/PanelFileInput.tsx
+++ b/packages/mantine/src/panel/PanelFileInput.tsx
@@ -1,7 +1,23 @@
import * as Mantine from "@mantine/core";
-import { PanelFileInputProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const PanelFileInput = (props: PanelFileInputProps) => (
-
-);
+export const PanelFileInput = forwardRef<
+ HTMLButtonElement,
+ ComponentProps["ImagePanel"]["FileInput"]
+>((props, ref) => {
+ const { className, value, placeholder, onChange, ...rest } = props;
+
+ return (
+
+ );
+});
diff --git a/packages/mantine/src/panel/PanelTab.tsx b/packages/mantine/src/panel/PanelTab.tsx
index 6d2dc64148..58ed2aba46 100644
--- a/packages/mantine/src/panel/PanelTab.tsx
+++ b/packages/mantine/src/panel/PanelTab.tsx
@@ -1,15 +1,15 @@
-import { mergeCSSClasses } from "@blocknote/core";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-import { PanelTabProps } from "@blocknote/react";
-
-export const PanelTab = (props: PanelTabProps) => {
- const { className, children, ...rest } = props;
+export const PanelTab = forwardRef<
+ HTMLDivElement,
+ ComponentProps["ImagePanel"]["TabPanel"]
+>((props, ref) => {
+ const { className, children } = props;
return (
-
+
{children}
);
-};
+});
diff --git a/packages/mantine/src/panel/PanelTextInput.tsx b/packages/mantine/src/panel/PanelTextInput.tsx
index 8436db9c34..59f6f9b884 100644
--- a/packages/mantine/src/panel/PanelTextInput.tsx
+++ b/packages/mantine/src/panel/PanelTextInput.tsx
@@ -1,9 +1,24 @@
import * as Mantine from "@mantine/core";
-import { PanelTextInputProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const PanelTextInput = (props: PanelTextInputProps) => {
- const { children, ...rest } = props;
+export const PanelTextInput = forwardRef<
+ HTMLInputElement,
+ ComponentProps["ImagePanel"]["TextInput"]
+>((props, ref) => {
+ const { className, value, placeholder, onKeyDown, onChange } = props;
- return
;
-};
+ return (
+
+ );
+});
diff --git a/packages/mantine/src/popover/Popover.tsx b/packages/mantine/src/popover/Popover.tsx
index 078f11509f..ccc1a74aeb 100644
--- a/packages/mantine/src/popover/Popover.tsx
+++ b/packages/mantine/src/popover/Popover.tsx
@@ -1,11 +1,41 @@
import * as Mantine from "@mantine/core";
-export const Popover = (props: Mantine.PopoverProps) => {
- const { children, ...rest } = props;
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const Popover = (
+ props: ComponentProps["Generic"]["Popover"]["Root"]
+) => {
+ const { children, opened, position } = props;
return (
-
+
{children}
);
};
+
+export const PopoverTrigger = (
+ props: ComponentProps["Generic"]["Popover"]["Trigger"]
+) => {
+ const { children } = props;
+
+ return {children};
+};
+
+export const PopoverContent = forwardRef<
+ HTMLDivElement,
+ ComponentProps["Generic"]["Popover"]["Content"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/mantine/src/sideMenu/SideMenu.tsx b/packages/mantine/src/sideMenu/SideMenu.tsx
new file mode 100644
index 0000000000..7a233730d8
--- /dev/null
+++ b/packages/mantine/src/sideMenu/SideMenu.tsx
@@ -0,0 +1,17 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SideMenu = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SideMenu"]["Root"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/mantine/src/sideMenu/SideMenuButton.tsx b/packages/mantine/src/sideMenu/SideMenuButton.tsx
new file mode 100644
index 0000000000..329f0baf4f
--- /dev/null
+++ b/packages/mantine/src/sideMenu/SideMenuButton.tsx
@@ -0,0 +1,30 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SideMenuButton = forwardRef<
+ HTMLButtonElement,
+ ComponentProps["SideMenu"]["Button"]
+>((props, ref) => {
+ const { className, children, icon, onClick, ...rest } = props;
+
+ if (icon) {
+ return (
+
+ {icon}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/mantine/src/style.css b/packages/mantine/src/style.css
new file mode 100644
index 0000000000..3f9aa96923
--- /dev/null
+++ b/packages/mantine/src/style.css
@@ -0,0 +1,490 @@
+@import url("@blocknote/react/style.css");
+@import url("./mantineStyles.css");
+
+/* Mantine base styles*/
+
+/* Mantine Badge component base styles */
+.bn-container .mantine-Badge-root {
+ background-color: var(--bn-colors-tooltip-background);
+ color: var(--bn-colors-tooltip-text);
+}
+
+/* Mantine FileInput component base styles */
+.bn-container .mantine-FileInput-input {
+ align-items: center;
+ background-color: var(--bn-colors-menu-background);
+ border: none;
+ border-radius: 4px;
+ color: var(--bn-colors-menu-text);
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+
+.bn-container .mantine-FileInput-input:hover {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+.bn-container .mantine-FileInput-wrapper {
+ border: solid var(--bn-colors-border) 1px;
+ border-radius: 4px;
+}
+
+.bn-container .mantine-InputPlaceholder-placeholder {
+ color: var(--bn-colors-menu-text);
+ font-weight: 600;
+}
+
+/* Mantine Menu component base styles */
+.bn-container .mantine-Menu-dropdown {
+ background-color: var(--bn-colors-menu-background);
+ border: var(--bn-border);
+ border-radius: var(--bn-border-radius-medium);
+ box-shadow: var(--bn-shadow-medium);
+ box-sizing: border-box;
+ color: var(--bn-colors-menu-text);
+ padding: 2px;
+ overflow: auto;
+}
+
+.bn-container .mantine-Menu-label {
+ background-color: var(--bn-colors-menu-background);
+ color: var(--bn-colors-menu-text);
+}
+
+.bn-container .mantine-Menu-item {
+ background-color: var(--bn-colors-menu-background);
+ border: none;
+ border-radius: var(--bn-border-radius-small);
+ color: var(--bn-colors-menu-text);
+}
+
+.bn-container .mantine-Menu-item[data-hovered] {
+ background-color: var(--bn-colors-hovered-background);
+ border: none;
+ color: var(--bn-colors-hovered-text);
+}
+
+/* Mantine Popover component base styles */
+.bn-container .mantine-Popover-dropdown {
+ background-color: transparent;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ padding: 0;
+}
+
+/* Mantine Tabs component base styles */
+.bn-container .mantine-Tabs-root {
+ width: 100%;
+ background-color: var(--bn-colors-menu-background);
+}
+
+.bn-container .mantine-Tabs-list:before {
+ border-color: var(--bn-colors-hovered-background);
+}
+
+.bn-container .mantine-Tabs-tab {
+ color: var(--bn-colors-menu-text);
+ border-color: var(--bn-colors-hovered-background);
+}
+
+.bn-container .mantine-Tabs-tab:hover {
+ background-color: var(--bn-colors-hovered-background);
+ border-color: var(--bn-colors-hovered-background);
+ color: var(--bn-colors-hovered-text);
+}
+
+.bn-container .mantine-Tabs-tab[data-active],
+.bn-container .mantine-Tabs-tab[data-active]:hover {
+ border-color: var(--bn-colors-menu-text);
+ color: var(--bn-colors-menu-text);
+}
+
+.bn-container .mantine-Tabs-panel {
+ padding: 8px;
+}
+
+/* Mantine TextInput component base styles */
+.bn-container .mantine-TextInput-input {
+ background-color: var(--bn-colors-menu-background);
+ border: solid var(--bn-colors-border) 1px;
+ border-radius: 4px;
+ color: var(--bn-colors-menu-text);
+ height: 32px;
+}
+
+/* Mantine Tooltip component base styles */
+.bn-container .mantine-Tooltip-tooltip {
+ background-color: transparent;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ padding: 0;
+}
+
+/* UI element styling */
+
+/* Select styling */
+.bn-select {
+ overflow: auto;
+}
+
+/* Toolbar styling */
+.bn-toolbar {
+ background-color: var(--bn-colors-menu-background);
+ border: var(--bn-border);
+ border-radius: var(--bn-border-radius-medium);
+ box-shadow: var(--bn-shadow-medium);
+ flex-wrap: nowrap;
+ gap: 2px;
+ padding: 2px;
+ width: fit-content;
+}
+
+.bn-toolbar:empty {
+ display: none;
+}
+
+.bn-toolbar .mantine-Button-root,
+.bn-toolbar .mantine-ActionIcon-root {
+ background-color: var(--bn-colors-menu-background);
+ border: none;
+ border-radius: var(--bn-border-radius-small);
+ color: var(--bn-colors-menu-text);
+}
+
+.bn-toolbar .mantine-Button-root:hover,
+.bn-toolbar .mantine-ActionIcon-root:hover {
+ background-color: var(--bn-colors-hovered-background);
+ border: none;
+ color: var(--bn-colors-hovered-text);
+}
+
+.bn-toolbar .mantine-Button-root[data-selected],
+.bn-toolbar .mantine-ActionIcon-root[data-selected] {
+ background-color: var(--bn-colors-selected-background);
+ border: none;
+ color: var(--bn-colors-selected-text);
+}
+
+.bn-toolbar .mantine-Button-root[data-disabled],
+.bn-toolbar .mantine-ActionIcon-root[data-disabled] {
+ background-color: var(--bn-colors-disabled-background);
+ border: none;
+ color: var(--bn-colors-disabled-text);
+}
+
+.bn-toolbar .mantine-Menu-item {
+ font-size: 12px;
+ height: 30px;
+}
+
+.bn-toolbar .mantine-Menu-item:hover {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+.bn-container .bn-form-popover {
+ background-color: var(--bn-colors-menu-background);
+ border: var(--bn-border);
+ border-radius: var(--bn-border-radius-medium);
+ box-shadow: var(--bn-shadow-medium);
+ color: var(--bn-colors-menu-text);
+ gap: 4px;
+ min-width: 145px;
+ padding: 2px;
+}
+
+.bn-form-popover .mantine-TextInput-root,
+.bn-form-popover .mantine-FileInput-root {
+ width: 300px;
+}
+
+.bn-form-popover .mantine-TextInput-wrapper,
+.bn-form-popover .mantine-FileInput-wrapper {
+ padding: 0;
+ border-radius: 4px;
+}
+
+.bn-form-popover .mantine-TextInput-wrapper:hover {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+.bn-form-popover .mantine-TextInput-input,
+.bn-form-popover .mantine-FileInput-input {
+ border: none;
+ font-size: 12px;
+}
+
+.bn-form-popover .mantine-FileInput-input:hover {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+.bn-form-popover .mantine-FileInput-section[data-position="left"] {
+ color: var(--bn-colors-menu-text);
+}
+
+.bn-form-popover .mantine-FileInput-placeholder {
+ color: var(--bn-colors-menu-text);
+}
+
+/* Suggestion Menu styling*/
+
+/*TODO: Make sure there's a clear split between copied Mantine Menu styles and
+ additional BlockNote styles*/
+
+/* Base styles for Suggestion Menus, copied from the Mantine Menu component. */
+/* Unfortunately necessary, as we can't use a Menu.Dropdown component on its
+ own. */
+/* https://github.com/mantinedev/mantine/blob/e3e3bb834de1f2f75a27dbc757dc0a2fc6a6cba8/packages/%40mantine/core/src/components/Menu/Menu.module.css */
+.bn-suggestion-menu {
+ max-height: 100%;
+ position: relative;
+ box-shadow: var(--mantine-shadow-md);
+ border: calc(0.0625rem * var(--mantine-scale)) solid
+ var(--mantine-color-gray-2);
+ border-radius: var(--mantine-radius-default);
+ padding: 4px;
+}
+
+.bn-suggestion-menu-label {
+ color: var(--mantine-color-dimmed);
+ font-weight: 500;
+ font-size: var(--mantine-font-size-xs);
+ padding: calc(var(--mantine-spacing-xs) / 2) var(--mantine-spacing-sm);
+ cursor: default;
+}
+
+.bn-suggestion-menu-item {
+ font-size: var(--mantine-font-size-sm);
+ width: 100%;
+ padding: calc(var(--mantine-spacing-xs) / 1.5) var(--mantine-spacing-sm);
+ border-radius: var(--popover-radius, var(--mantine-radius-default));
+ color: var(--menu-item-color, var(--mantine-color-text));
+ display: flex;
+ align-items: center;
+ user-select: none;
+
+ &:where([data-disabled], :disabled) {
+ color: var(--mantine-color-dimmed);
+ opacity: 0.6;
+ pointer-events: none;
+ }
+
+ &:where([data-hovered]) {
+ }
+}
+
+.bn-mt-suggestion-menu-item-body {
+ flex: 1;
+}
+
+.bn-mt-suggestion-menu-item-section {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:where([data-position="left"]) {
+ margin-inline-end: var(--mantine-spacing-xs);
+ }
+
+ &:where([data-position="right"]) {
+ margin-inline-start: var(--mantine-spacing-xs);
+ }
+}
+
+.bn-suggestion-menu {
+ background-color: var(--bn-colors-menu-background);
+ border: var(--bn-border);
+ border-radius: var(--bn-border-radius-medium);
+ box-shadow: var(--bn-shadow-medium);
+ box-sizing: border-box;
+ color: var(--bn-colors-menu-text);
+ overflow-y: auto;
+ padding: 2px;
+}
+
+.bn-suggestion-menu-item {
+ cursor: pointer;
+ height: 52px;
+}
+
+.bn-suggestion-menu-item[data-hovered] {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+.bn-mt-suggestion-menu-item-section {
+ color: var(--bn-colors-tooltip-text);
+}
+
+.bn-mt-suggestion-menu-item-section[data-position="left"] {
+ background-color: var(--bn-colors-tooltip-background);
+ border-radius: var(--bn-border-radius-small);
+ padding: 8px;
+}
+
+.bn-mt-suggestion-menu-item-body {
+ align-items: stretch;
+ color: var(--bn-colors-menu-text);
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ justify-content: flex-start;
+ padding-right: 16px;
+}
+
+.bn-mt-suggestion-menu-item-title {
+ line-height: 20px;
+ font-weight: 500;
+ font-size: 14px;
+ margin: 0;
+ padding: 0;
+}
+
+.bn-mt-suggestion-menu-item-subtitle {
+ line-height: 16px;
+ font-size: 10px;
+ margin: 0;
+ padding: 0;
+}
+
+.bn-suggestion-menu-label {
+ color: var(--bn-colors-hovered-text);
+}
+
+.bn-suggestion-menu-loader {
+ height: 20px;
+ width: 100%;
+}
+
+.bn-suggestion-menu-loader span {
+ background-color: var(--bn-colors-side-menu);
+}
+
+/* Side Menu styling */
+.bn-side-menu {
+ background-color: transparent;
+ overflow: visible;
+}
+
+.bn-side-menu .mantine-Menu-item, .bn-table-handle-menu .mantine-Menu-item {
+ font-size: 12px;
+ height: 30px;
+}
+
+.bn-side-menu .mantine-UnstyledButton-root:not(.mantine-Menu-item) {
+ background-color: transparent;
+}
+
+.bn-side-menu .mantine-UnstyledButton-root:hover {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+.bn-side-menu .mantine-UnstyledButton-root:not(.mantine-Menu-item) svg {
+ background-color: transparent;
+ color: var(--bn-colors-side-menu);
+ height: 22px;
+ width: 22px;
+}
+
+.bn-side-menu > [draggable="true"] {
+ display: flex;
+}
+
+.bn-side-menu .mantine-Menu-dropdown {
+ min-width: 100px;
+ padding: 2px;
+ position: absolute;
+}
+
+/* Image Panel styling*/
+.bn-panel {
+ background-color: var(--bn-colors-menu-background);
+ border: var(--bn-border);
+ border-radius: var(--bn-border-radius-medium);
+ box-shadow: var(--bn-shadow-medium);
+ padding: 2px;
+ width: 500px;
+}
+
+.bn-panel .bn-tab-panel {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+}
+
+.bn-panel .mantine-TextInput-root,
+.bn-panel .mantine-FileInput-root {
+ width: 100%;
+}
+
+.bn-panel .mantine-Button-root {
+ background-color: var(--bn-colors-menu-background);
+ border: solid var(--bn-colors-border) 1px;
+ border-radius: var(--bn-border-radius-small);
+ color: var(--bn-colors-menu-text);
+ height: 32px;
+ width: 60%;
+}
+
+.bn-panel .mantine-Button-root:hover {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+.bn-panel.mantine-Text-root {
+ text-align: center;
+}
+
+/* Table Handle styling */
+.bn-table-handle {
+ align-items: center;
+ background-color: var(--bn-colors-menu-background);
+ border: var(--bn-border);
+ border-radius: var(--bn-border-radius-small);
+ box-shadow: var(--bn-shadow-light);
+ color: var(--bn-colors-side-menu);
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ overflow: visible;
+ padding: 0;
+}
+
+.bn-table-handle svg {
+ margin-inline: -4px;
+}
+
+.bn-table-handle:hover,
+.bn-table-handle-dragging {
+ background-color: var(--bn-colors-hovered-background);
+}
+
+/* Drag Handle & Table Handle Menu styling */
+.bn-container .bn-drag-handle-menu {
+ overflow: visible;
+}
+
+/* Tooltip styling */
+.bn-tooltip {
+ background-color: var(--bn-colors-tooltip-background);
+ border: var(--bn-border);
+ border-radius: var(--bn-border-radius-medium);
+ box-shadow: var(--bn-shadow-medium);
+ color: var(--bn-colors-tooltip-text);
+ padding: 4px 10px;
+ text-align: center;
+}
+
+/* Additional menu styles */
+.bn-tick-space {
+ padding: 0;
+ width: 20px;
+}
+
+.bn-mt-sub-menu-item > .mantine-Menu-itemLabel > div:not(.mantine-Menu-dropdown) {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+}
diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx
new file mode 100644
index 0000000000..4c0e5a6db7
--- /dev/null
+++ b/packages/mantine/src/suggestionMenu/SuggestionMenu.tsx
@@ -0,0 +1,17 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenu = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Root"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuEmptyItem.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuEmptyItem.tsx
new file mode 100644
index 0000000000..3e88792d86
--- /dev/null
+++ b/packages/mantine/src/suggestionMenu/SuggestionMenuEmptyItem.tsx
@@ -0,0 +1,19 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenuEmptyItem = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["EmptyItem"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+
+ {children}
+
+
+ );
+});
diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx
new file mode 100644
index 0000000000..bc622bb073
--- /dev/null
+++ b/packages/mantine/src/suggestionMenu/SuggestionMenuItem.tsx
@@ -0,0 +1,67 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef, useCallback } from "react";
+
+export const SuggestionMenuItem = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Item"]
+>((props, ref) => {
+ const {
+ className,
+ title,
+ subtext,
+ // group,
+ icon,
+ badge,
+ // aliases,
+ // onItemClick,
+ isSelected,
+ setSelected,
+ onClick,
+ } = props;
+
+ const handleMouseLeave = useCallback(() => {
+ setSelected?.(false);
+ }, [setSelected]);
+
+ const handleMouseEnter = useCallback(() => {
+ setSelected?.(true);
+ }, [setSelected]);
+
+ return (
+
+ {icon && (
+
+ {icon}
+
+ )}
+
+
+ {title}
+
+
+ {subtext}
+
+
+ {badge && (
+
+ {badge}
+
+ )}
+
+ );
+});
diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx
new file mode 100644
index 0000000000..9c7178cc9b
--- /dev/null
+++ b/packages/mantine/src/suggestionMenu/SuggestionMenuLabel.tsx
@@ -0,0 +1,17 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenuLabel = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Label"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/mantine/src/suggestionMenu/SuggestionMenuLoader.tsx b/packages/mantine/src/suggestionMenu/SuggestionMenuLoader.tsx
new file mode 100644
index 0000000000..c68760f42e
--- /dev/null
+++ b/packages/mantine/src/suggestionMenu/SuggestionMenuLoader.tsx
@@ -0,0 +1,17 @@
+import * as Mantine from "@mantine/core";
+
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const SuggestionMenuLoader = forwardRef<
+ HTMLDivElement,
+ ComponentProps["SuggestionMenu"]["Loader"]
+>((props, ref) => {
+ const { className, children } = props;
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/mantine/src/tableHandle/TableHandle.tsx b/packages/mantine/src/tableHandle/TableHandle.tsx
new file mode 100644
index 0000000000..08ed50271e
--- /dev/null
+++ b/packages/mantine/src/tableHandle/TableHandle.tsx
@@ -0,0 +1,30 @@
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
+
+export const TableHandle = forwardRef<
+ HTMLButtonElement,
+ ComponentProps["TableHandle"]["Root"]
+>((props, ref) => {
+ const {
+ className,
+ children,
+ draggable,
+ onDragStart,
+ onDragEnd,
+ style,
+ ...rest
+ } = props;
+
+ return (
+
+ );
+});
diff --git a/packages/mantine/src/toolbar/Toolbar.tsx b/packages/mantine/src/toolbar/Toolbar.tsx
index bfacaf6fdc..1ab8f33a64 100644
--- a/packages/mantine/src/toolbar/Toolbar.tsx
+++ b/packages/mantine/src/toolbar/Toolbar.tsx
@@ -1,20 +1,23 @@
import * as Mantine from "@mantine/core";
-import { mergeCSSClasses } from "@blocknote/core";
-import { forwardRef, HTMLAttributes } from "react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
-export const Toolbar = forwardRef<
- HTMLDivElement,
- HTMLAttributes
->((props, ref) => {
- const { className, children, ...rest } = props;
+type ToolbarProps = ComponentProps["FormattingToolbar"]["Root"] &
+ ComponentProps["LinkToolbar"]["Root"];
- return (
-
- {children}
-
- );
-});
+export const Toolbar = forwardRef(
+ (props, ref) => {
+ const { className, children, onMouseEnter, onMouseLeave } = props;
+
+ return (
+
+ {children}
+
+ );
+ }
+);
diff --git a/packages/mantine/src/toolbar/ToolbarButton.tsx b/packages/mantine/src/toolbar/ToolbarButton.tsx
index 9ac1aa02d3..d6a6ccfe5d 100644
--- a/packages/mantine/src/toolbar/ToolbarButton.tsx
+++ b/packages/mantine/src/toolbar/ToolbarButton.tsx
@@ -1,7 +1,7 @@
import * as Mantine from "@mantine/core";
import { isSafari } from "@blocknote/core";
-import { ToolbarButtonProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
import { forwardRef } from "react";
export const TooltipContent = (props: {
@@ -16,23 +16,39 @@ export const TooltipContent = (props: {
);
+type ToolbarButtonProps = ComponentProps["FormattingToolbar"]["Button"] &
+ ComponentProps["LinkToolbar"]["Button"];
+
/**
* Helper for basic buttons that show in the formatting toolbar.
*/
export const ToolbarButton = forwardRef(
(props, ref) => {
+ const {
+ className,
+ children,
+ mainTooltip,
+ secondaryTooltip,
+ icon,
+ isSelected,
+ isDisabled,
+ onClick,
+ ...rest
+ } = props;
+
return (
}>
{/*Creates an ActionIcon instead of a Button if only an icon is provided as content.*/}
- {props.children ? (
+ {children ? (
{
@@ -40,20 +56,21 @@ export const ToolbarButton = forwardRef(
(e.currentTarget as HTMLButtonElement).focus();
}
}}
- onClick={props.onClick}
- data-selected={props.isSelected ? "true" : undefined}
+ onClick={onClick}
+ data-selected={isSelected ? "true" : undefined}
data-test={
- props.mainTooltip.slice(0, 1).toLowerCase() +
- props.mainTooltip.replace(/\s+/g, "").slice(1)
+ mainTooltip.slice(0, 1).toLowerCase() +
+ mainTooltip.replace(/\s+/g, "").slice(1)
}
size={"xs"}
- disabled={props.isDisabled || false}
- ref={ref}>
- {props.icon}
- {props.children}
+ disabled={isDisabled || false}
+ ref={ref}
+ {...rest}>
+ {children}
) : (
{
@@ -61,16 +78,17 @@ export const ToolbarButton = forwardRef(
(e.currentTarget as HTMLButtonElement).focus();
}
}}
- onClick={props.onClick}
- data-selected={props.isSelected ? "true" : undefined}
+ onClick={onClick}
+ data-selected={isSelected ? "true" : undefined}
data-test={
- props.mainTooltip.slice(0, 1).toLowerCase() +
- props.mainTooltip.replace(/\s+/g, "").slice(1)
+ mainTooltip.slice(0, 1).toLowerCase() +
+ mainTooltip.replace(/\s+/g, "").slice(1)
}
size={30}
- disabled={props.isDisabled || false}
- ref={ref}>
- {props.icon}
+ disabled={isDisabled || false}
+ ref={ref}
+ {...rest}>
+ {icon}
)}
diff --git a/packages/mantine/src/toolbar/ToolbarInputsMenuItem.tsx b/packages/mantine/src/toolbar/ToolbarInputsMenuItem.tsx
index 4a157b510e..cfc26e8b41 100644
--- a/packages/mantine/src/toolbar/ToolbarInputsMenuItem.tsx
+++ b/packages/mantine/src/toolbar/ToolbarInputsMenuItem.tsx
@@ -2,6 +2,7 @@ import * as Mantine from "@mantine/core";
import type { IconType } from "react-icons";
+// TODO
export type ToolbarInputsMenuItemProps = {
icon: IconType;
} & Mantine.TextInputProps;
diff --git a/packages/mantine/src/toolbar/ToolbarSelect.tsx b/packages/mantine/src/toolbar/ToolbarSelect.tsx
index 8488a41571..7f75969dbd 100644
--- a/packages/mantine/src/toolbar/ToolbarSelect.tsx
+++ b/packages/mantine/src/toolbar/ToolbarSelect.tsx
@@ -1,14 +1,18 @@
import * as Mantine from "@mantine/core";
import { isSafari } from "@blocknote/core";
-import { ToolbarSelectProps } from "@blocknote/react";
+import { ComponentProps } from "@blocknote/react";
+import { forwardRef } from "react";
import { HiChevronDown } from "react-icons/hi";
-import { ToolbarSelectItem } from "./ToolbarSelectItem";
-
// TODO: turn into select
-export function ToolbarSelect(props: ToolbarSelectProps) {
- const selectedItem = props.items.filter((p) => p.isSelected)[0];
+export const ToolbarSelect = forwardRef<
+ HTMLDivElement,
+ ComponentProps["FormattingToolbar"]["Select"]
+>((props, ref) => {
+ const { className, items, isDisabled } = props;
+
+ const selectedItem = items.filter((p) => p.isSelected)[0];
if (!selectedItem) {
return null;
@@ -20,7 +24,7 @@ export function ToolbarSelect(props: ToolbarSelectProps) {
transitionProps={{
exitDuration: 0,
}}
- disabled={props.isDisabled}
+ disabled={isDisabled}
middlewares={{ flip: true, shift: true, inline: false, size: true }}>
}
size={"xs"}
variant={"subtle"}
- disabled={props.isDisabled}>
+ disabled={isDisabled}>
{selectedItem.text}
-
- {props.items.map((item) => (
-
+
+ {items.map((item) => (
+
+ ) : (
+ // Ensures space for tick even if item isn't currently selected.
+
+ )
+ }
+ disabled={item.isDisabled}>
+ {item.text}
+
))}
);
-}
+});
diff --git a/packages/mantine/src/toolbar/ToolbarSelectItem.tsx b/packages/mantine/src/toolbar/ToolbarSelectItem.tsx
deleted file mode 100644
index c9e6c162f0..0000000000
--- a/packages/mantine/src/toolbar/ToolbarSelectItem.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as Mantine from "@mantine/core";
-
-import { ToolbarSelectItemProps } from "@blocknote/react";
-import { TiTick } from "react-icons/ti";
-
-export function ToolbarSelectItem(props: ToolbarSelectItemProps) {
- return (
-
- ) : (
- // Ensures space for tick even if item isn't currently selected.
-
- )
- }
- disabled={props.isDisabled}>
- {props.text}
-
- );
-}
diff --git a/packages/mantine/tsconfig.json b/packages/mantine/tsconfig.json
index 74774570bc..607ad93cfa 100644
--- a/packages/mantine/tsconfig.json
+++ b/packages/mantine/tsconfig.json
@@ -19,9 +19,6 @@
"declarationDir": "types",
"composite": true,
"skipLibCheck": true,
- "paths": {
- "@/*": ["./src/*"]
- }
},
"include": ["src"],
"references": [
diff --git a/packages/mantine/vite.config.ts b/packages/mantine/vite.config.ts
index 346fed5e93..0e9e1ed2f5 100644
--- a/packages/mantine/vite.config.ts
+++ b/packages/mantine/vite.config.ts
@@ -16,11 +16,8 @@ export default defineConfig((conf) => ({
resolve: {
alias:
conf.command === "build"
- ? ({
- "@": path.resolve(__dirname, "./src"),
- } as Record)
+ ? ({} as Record)
: ({
- "@": path.resolve(__dirname, "./src"),
// load live from sources with live reload working
"@blocknote/core": path.resolve(__dirname, "../core/src/"),
"@blocknote/react": path.resolve(__dirname, "../react/src/"),
@@ -30,8 +27,8 @@ export default defineConfig((conf) => ({
sourcemap: true,
lib: {
entry: path.resolve(__dirname, "src/index.tsx"),
- name: "blocknote-shadcn",
- fileName: "blocknote-shadcn",
+ name: "blocknote-mantine",
+ fileName: "blocknote-mantine",
},
rollupOptions: {
// make sure to externalize deps that shouldn't be bundled
diff --git a/packages/react/src/components/ColorPicker/ColorPicker.tsx b/packages/react/src/components/ColorPicker/ColorPicker.tsx
index e60617f3e7..5c73c1f4ce 100644
--- a/packages/react/src/components/ColorPicker/ColorPicker.tsx
+++ b/packages/react/src/components/ColorPicker/ColorPicker.tsx
@@ -1,6 +1,20 @@
import { useComponentsContext } from "../../editor/ComponentsContext";
+import { useDictionaryContext } from "../../i18n/dictionary";
import { ColorIcon } from "./ColorIcon";
+const colors = [
+ "default",
+ "gray",
+ "brown",
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "blue",
+ "purple",
+ "pink",
+] as const;
+
export const ColorPicker = (props: {
onClick?: () => void;
iconSize?: number;
@@ -13,25 +27,16 @@ export const ColorPicker = (props: {
setColor: (color: string) => void;
};
}) => {
- const components = useComponentsContext()!;
-
+ const Components = useComponentsContext()!;
+ const dict = useDictionaryContext();
const TextColorSection = () =>
props.text ? (
<>
- Text
- {[
- "default",
- "gray",
- "brown",
- "red",
- "orange",
- "yellow",
- "green",
- "blue",
- "purple",
- "pink",
- ].map((color) => (
-
+ {dict.color_picker.text_title}
+
+ {colors.map((color) => (
+ {
props.onClick && props.onClick();
props.text!.setColor(color);
@@ -40,8 +45,8 @@ export const ColorPicker = (props: {
icon={}
checked={props.text!.color === color}
key={"text-color-" + color}>
- {color.charAt(0).toUpperCase() + color.slice(1)}
-
+ {dict.color_picker.colors[color]}
+
))}
>
) : null;
@@ -49,20 +54,11 @@ export const ColorPicker = (props: {
const BackgroundColorSection = () =>
props.background ? (
<>
- Background
- {[
- "default",
- "gray",
- "brown",
- "red",
- "orange",
- "yellow",
- "green",
- "blue",
- "purple",
- "pink",
- ].map((color) => (
-
+ {dict.color_picker.background_title}
+
+ {colors.map((color) => (
+ {
props.onClick && props.onClick();
props.background!.setColor(color);
@@ -71,8 +67,8 @@ export const ColorPicker = (props: {
icon={}
key={"background-color-" + color}
checked={props.background!.color === color}>
- {color.charAt(0).toUpperCase() + color.slice(1)}
-
+ {dict.color_picker.colors[color]}
+
))}
>
) : null;
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx
index eccc62fec7..f4194be708 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/BasicTextStyleButton.tsx
@@ -19,6 +19,7 @@ import { useComponentsContext } from "../../../editor/ComponentsContext";
import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor";
import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange";
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks";
+import { useDictionaryContext } from "../../../i18n/dictionary";
type BasicTextStyle = "bold" | "italic" | "underline" | "strike" | "code";
@@ -30,14 +31,6 @@ const icons = {
code: RiCodeFill,
} satisfies Record;
-const shortcuts = {
- bold: "Mod+B",
- italic: "Mod+I",
- underline: "Mod+U",
- strike: "Mod+Shift+X",
- code: "",
-} satisfies Record;
-
function checkBasicTextStyleInSchema