diff --git a/package-lock.json b/package-lock.json
index 11a6ece4f1..2a71ba0847 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5429,6 +5429,36 @@
}
}
},
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz",
+ "integrity": "sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.1",
+ "@radix-ui/react-context": "1.0.1",
+ "@radix-ui/react-direction": "1.0.1",
+ "@radix-ui/react-id": "1.0.1",
+ "@radix-ui/react-presence": "1.0.1",
+ "@radix-ui/react-primitive": "1.0.3",
+ "@radix-ui/react-roving-focus": "1.0.4",
+ "@radix-ui/react-use-controllable-state": "1.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-toggle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz",
@@ -23119,6 +23149,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"class-variance-authority": "^0.7.0",
diff --git a/packages/react/src/ariakit/ariakit.css b/packages/react/src/ariakit/ariakit.css
index 87d014744c..61493d7e06 100644
--- a/packages/react/src/ariakit/ariakit.css
+++ b/packages/react/src/ariakit/ariakit.css
@@ -182,6 +182,128 @@
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%);
+}
+
+.input:focus-visible,
+.input[data-focus-visible] {
+ outline: 2px solid hsl(204 100% 40%);
+ outline-offset: -1px;
+}
+
+: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);
+}
+
+:is(.dark .input)::placeholder {
+ color: hsl(204 20% 100% / 46%);
+}
+
+:is(.dark .input:hover) {
+ background-color: hsl(204 4% 8%);
+}
+
+: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);
+}
+
+.tab-list {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.tab {
+ display: flex;
+ height: 2.5rem;
+ user-select: none;
+ align-items: center;
+ 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%);
+}
+
+.panel {
+ padding: 0.5rem;
+}
+
.bn-menu {
position: relative;
z-index: 50;
@@ -262,3 +384,16 @@
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);
+}
+
diff --git a/packages/react/src/ariakit/components.tsx b/packages/react/src/ariakit/components.tsx
index b20eb8310c..634cc6ed5a 100644
--- a/packages/react/src/ariakit/components.tsx
+++ b/packages/react/src/ariakit/components.tsx
@@ -15,6 +15,12 @@ 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";
+
export const ariakitComponents: ComponentsContextValue = {
Form,
TextInput,
@@ -27,6 +33,11 @@ export const ariakitComponents: ComponentsContextValue = {
MenuDivider,
MenuLabel,
MenuItem,
+ Panel,
+ PanelButton,
+ PanelFileInput,
+ PanelTab,
+ PanelTextInput,
Popover,
PopoverContent,
PopoverTrigger,
diff --git a/packages/react/src/ariakit/panel/Panel.tsx b/packages/react/src/ariakit/panel/Panel.tsx
new file mode 100644
index 0000000000..ded7c08241
--- /dev/null
+++ b/packages/react/src/ariakit/panel/Panel.tsx
@@ -0,0 +1,33 @@
+import * as Ariakit from "@ariakit/react";
+
+import { PanelProps } from "../../editor/ComponentsContext";
+
+export const Panel = (props: PanelProps) => {
+ return (
+
+
{
+ if (activeId) {
+ props.setOpenTab(activeId);
+ }
+ }}>
+ {/*{props.loading && }*/}
+
+
+ {props.tabs.map((tab) => (
+
+ {tab.name}
+
+ ))}
+
+
+ {props.tabs.map((tab) => (
+
+ {tab.tabPanel}
+
+ ))}
+
+
+ );
+};
diff --git a/packages/react/src/ariakit/panel/PanelButton.tsx b/packages/react/src/ariakit/panel/PanelButton.tsx
new file mode 100644
index 0000000000..9c6a6345bc
--- /dev/null
+++ b/packages/react/src/ariakit/panel/PanelButton.tsx
@@ -0,0 +1,16 @@
+import * as Ariakit from "@ariakit/react";
+
+import { PanelButtonProps } from "../../editor/ComponentsContext";
+import { mergeCSSClasses } from "@blocknote/core";
+
+export const PanelButton = (props: PanelButtonProps) => {
+ const { children, className, ...rest } = props;
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/react/src/ariakit/panel/PanelFileInput.tsx b/packages/react/src/ariakit/panel/PanelFileInput.tsx
new file mode 100644
index 0000000000..96d03c01f7
--- /dev/null
+++ b/packages/react/src/ariakit/panel/PanelFileInput.tsx
@@ -0,0 +1,16 @@
+import * as Ariakit from "@ariakit/react";
+
+import { PanelFileInputProps } from "../../editor/ComponentsContext";
+
+export const PanelFileInput = (props: PanelFileInputProps) => (
+
+ props.onChange?.(e.target.files![0])}
+ placeholder={props.placeholder}
+ />
+
+);
diff --git a/packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx b/packages/react/src/ariakit/panel/PanelTab.tsx
similarity index 51%
rename from packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx
rename to packages/react/src/ariakit/panel/PanelTab.tsx
index cda386b139..dfb92e3fa9 100644
--- a/packages/react/src/components/ImagePanel/mantine/ImagePanelTab.tsx
+++ b/packages/react/src/ariakit/panel/PanelTab.tsx
@@ -1,18 +1,15 @@
import { mergeCSSClasses } from "@blocknote/core";
-import { ComponentPropsWithoutRef, forwardRef } from "react";
-export const ImagePanelTab = forwardRef<
- HTMLDivElement,
- ComponentPropsWithoutRef<"div">
->((props, ref) => {
+import { PanelTabProps } from "../../editor/ComponentsContext";
+
+export const PanelTab = (props: PanelTabProps) => {
const { className, children, ...rest } = props;
return (
+ {...rest}>
{children}
);
-});
+};
diff --git a/packages/react/src/ariakit/panel/PanelTextInput.tsx b/packages/react/src/ariakit/panel/PanelTextInput.tsx
new file mode 100644
index 0000000000..3a63326490
--- /dev/null
+++ b/packages/react/src/ariakit/panel/PanelTextInput.tsx
@@ -0,0 +1,18 @@
+import * as Ariakit from "@ariakit/react";
+
+import { PanelTextInputProps } from "../../editor/ComponentsContext";
+
+export const PanelTextInput = (props: PanelTextInputProps) => {
+ const { children, ...rest } = props;
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/ReplaceImageButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/ReplaceImageButton.tsx
index 7357733824..06bede1b04 100644
--- a/packages/react/src/components/FormattingToolbar/DefaultButtons/ReplaceImageButton.tsx
+++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/ReplaceImageButton.tsx
@@ -10,7 +10,7 @@ import { RiImageEditFill } from "react-icons/ri";
import { useComponentsContext } from "../../../editor/ComponentsContext";
import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor";
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks";
-import { ImagePanel } from "../../ImagePanel/mantine/ImagePanel";
+import { ImagePanel } from "../../ImagePanel/ImagePanel";
export const ReplaceImageButton = () => {
const components = useComponentsContext()!;
diff --git a/packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx b/packages/react/src/components/ImagePanel/DefaultTabs/EmbedTab.tsx
similarity index 79%
rename from packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx
rename to packages/react/src/components/ImagePanel/DefaultTabs/EmbedTab.tsx
index 542b9460aa..75eb1fb88a 100644
--- a/packages/react/src/components/ImagePanel/mantine/DefaultTabs/EmbedTab.tsx
+++ b/packages/react/src/components/ImagePanel/DefaultTabs/EmbedTab.tsx
@@ -6,12 +6,10 @@ import {
StyleSchema,
} from "@blocknote/core";
import { ChangeEvent, KeyboardEvent, useCallback, useState } from "react";
-import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor";
-import { ImagePanelProps } from "../../ImagePanelProps";
-import { ImagePanelButton } from "../ImagePanelButton";
-import { ImagePanelTab } from "../ImagePanelTab";
-import { ImagePanelTextInput } from "../ImagePanelTextInput";
+import { useComponentsContext } from "../../../editor/ComponentsContext";
+import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor";
+import { ImagePanelProps } from "../ImagePanelProps";
export const EmbedTab = <
I extends InlineContentSchema = DefaultInlineContentSchema,
@@ -19,6 +17,8 @@ export const EmbedTab = <
>(
props: ImagePanelProps
) => {
+ const components = useComponentsContext()!;
+
const { block } = props;
const editor = useBlockNoteEditor<
@@ -61,20 +61,20 @@ export const EmbedTab = <
}, [editor, block, currentURL]);
return (
-
-
+
-
Embed Image
-
-
+
+
);
};
diff --git a/packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx b/packages/react/src/components/ImagePanel/DefaultTabs/UploadTab.tsx
similarity index 81%
rename from packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx
rename to packages/react/src/components/ImagePanel/DefaultTabs/UploadTab.tsx
index cbc974eda7..875156bbf0 100644
--- a/packages/react/src/components/ImagePanel/mantine/DefaultTabs/UploadTab.tsx
+++ b/packages/react/src/components/ImagePanel/DefaultTabs/UploadTab.tsx
@@ -1,5 +1,4 @@
import { Text } from "@mantine/core";
-import { useBlockNoteEditor } from "../../../../hooks/useBlockNoteEditor";
import {
DefaultBlockSchema,
DefaultInlineContentSchema,
@@ -9,9 +8,9 @@ import {
} from "@blocknote/core";
import { useCallback, useEffect, useState } from "react";
-import { ImagePanelProps } from "../../ImagePanelProps";
-import { ImagePanelTab } from "../ImagePanelTab";
-import { ImagePanelFileInput } from "../ImagePanelFileInput";
+import { useComponentsContext } from "../../../editor/ComponentsContext";
+import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor";
+import { ImagePanelProps } from "../ImagePanelProps";
export const UploadTab = <
I extends InlineContentSchema = DefaultInlineContentSchema,
@@ -21,6 +20,8 @@ export const UploadTab = <
setLoading: (loading: boolean) => void;
}
) => {
+ const components = useComponentsContext()!;
+
const { block, setLoading } = props;
const editor = useBlockNoteEditor<
@@ -70,18 +71,19 @@ export const UploadTab = <
[block, editor, setLoading]
);
- return editor.uploadFile !== undefined ? (
-
-
+
{uploadFailed && (
Error: Upload failed
)}
-
- ) : null;
+
+ );
};
diff --git a/packages/react/src/components/ImagePanel/ImagePanel.tsx b/packages/react/src/components/ImagePanel/ImagePanel.tsx
new file mode 100644
index 0000000000..3a5ca46cec
--- /dev/null
+++ b/packages/react/src/components/ImagePanel/ImagePanel.tsx
@@ -0,0 +1,71 @@
+import {
+ DefaultBlockSchema,
+ DefaultInlineContentSchema,
+ DefaultStyleSchema,
+ InlineContentSchema,
+ StyleSchema,
+} from "@blocknote/core";
+import { useState } from "react";
+
+import {
+ PanelProps,
+ useComponentsContext,
+} from "../../editor/ComponentsContext";
+import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor";
+import { ImagePanelProps } from "./ImagePanelProps";
+import { EmbedTab } from "./DefaultTabs/EmbedTab";
+import { UploadTab } from "./DefaultTabs/UploadTab";
+
+/**
+ * By default, the ImageToolbar component will render with default tabs.
+ * However, you can override the tabs to render by passing the `tabs` prop. You
+ * can use the default tab panels in the `DefaultTabPanels` directory or make
+ * your own using the `ImageToolbarPanel` component.
+ */
+export const ImagePanel = <
+ I extends InlineContentSchema = DefaultInlineContentSchema,
+ S extends StyleSchema = DefaultStyleSchema
+>(
+ props: ImagePanelProps &
+ Partial>
+) => {
+ const components = useComponentsContext()!;
+
+ const editor = useBlockNoteEditor<
+ { image: DefaultBlockSchema["image"] },
+ I,
+ S
+ >();
+
+ const [loading, setLoading] = useState(false);
+
+ const tabs: PanelProps["tabs"] = props.tabs ?? [
+ ...(editor.uploadFile !== undefined
+ ? [
+ {
+ name: "Upload",
+ tabPanel: ,
+ },
+ ]
+ : []),
+ {
+ name: "Embed",
+ tabPanel: ,
+ },
+ ];
+
+ const [openTab, setOpenTab] = useState(
+ props.defaultOpenTab || tabs[0].name
+ );
+
+ return (
+
+ );
+};
diff --git a/packages/react/src/components/ImagePanel/ImagePanelController.tsx b/packages/react/src/components/ImagePanel/ImagePanelController.tsx
index adadd8a364..aee8039b7b 100644
--- a/packages/react/src/components/ImagePanel/ImagePanelController.tsx
+++ b/packages/react/src/components/ImagePanel/ImagePanelController.tsx
@@ -11,8 +11,8 @@ import { FC } from "react";
import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor";
import { useUIElementPositioning } from "../../hooks/useUIElementPositioning";
import { useUIPluginState } from "../../hooks/useUIPluginState";
+import { ImagePanel } from "./ImagePanel";
import { ImagePanelProps } from "./ImagePanelProps";
-import { ImagePanel } from "./mantine/ImagePanel";
export const ImagePanelController = <
I extends InlineContentSchema = DefaultInlineContentSchema,
diff --git a/packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx
deleted file mode 100644
index 24df5a4838..0000000000
--- a/packages/react/src/components/ImagePanel/mantine/ImagePanel.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import {
- DefaultBlockSchema,
- DefaultInlineContentSchema,
- DefaultStyleSchema,
- InlineContentSchema,
- StyleSchema,
-} from "@blocknote/core";
-import { Group, LoadingOverlay, Tabs } from "@mantine/core";
-import { ReactNode, useState } from "react";
-
-import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor";
-import { ImagePanelProps } from "../ImagePanelProps";
-import { EmbedTab } from "./DefaultTabs/EmbedTab";
-import { UploadTab } from "./DefaultTabs/UploadTab";
-
-/**
- * By default, the ImageToolbar component will render with default tabs.
- * However, you can override the tabs to render by passing the `tabs` prop. You
- * can use the default tab panels in the `DefaultTabPanels` directory or make
- * your own using the `ImageToolbarPanel` component.
- */
-export const ImagePanel = <
- I extends InlineContentSchema = DefaultInlineContentSchema,
- S extends StyleSchema = DefaultStyleSchema
->(
- props: ImagePanelProps & {
- children?: ReactNode;
- }
-) => {
- const editor = useBlockNoteEditor<
- { image: DefaultBlockSchema["image"] },
- I,
- S
- >();
-
- const [openTab, setOpenTab] = useState("default");
- const [loading, setLoading] = useState(false);
-
- return (
-
- {props.children !== undefined ? (
- props.children
- ) : (
-
- {loading && }
-
-
- {editor.uploadFile !== undefined && (
-
- Upload
-
- )}
-
- Embed
-
-
-
- {editor.uploadFile !== undefined && (
-
-
-
- )}
-
-
-
-
- )}
-
- );
-};
diff --git a/packages/react/src/components/ImagePanel/mantine/ImagePanelButton.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelButton.tsx
deleted file mode 100644
index 56ea90afe6..0000000000
--- a/packages/react/src/components/ImagePanel/mantine/ImagePanelButton.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { ComponentPropsWithoutRef, forwardRef } from "react";
-import { Button } from "@mantine/core";
-
-export const ImagePanelButton = forwardRef<
- HTMLButtonElement,
- Omit, "size">
->((props, ref) => (
-
-));
diff --git a/packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx
deleted file mode 100644
index 3ec61c5e72..0000000000
--- a/packages/react/src/components/ImagePanel/mantine/ImagePanelFileInput.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { FileInput } from "@mantine/core";
-import { ComponentPropsWithoutRef, forwardRef, ReactNode } from "react";
-
-export const ImagePanelFileInput = forwardRef<
- HTMLButtonElement,
- Omit<
- ComponentPropsWithoutRef<"button">,
- "value" | "defaultValue" | "onChange"
- > & {
- placeholder?: ReactNode;
- value?: File | null;
- defaultValue?: File | null;
- onChange?: (payload: File | null) => void;
- }
->((props, ref) => (
-
-));
diff --git a/packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx b/packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx
deleted file mode 100644
index c008ef7f7f..0000000000
--- a/packages/react/src/components/ImagePanel/mantine/ImagePanelTextInput.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { TextInput } from "@mantine/core";
-import { ComponentPropsWithoutRef, forwardRef } from "react";
-
-export const ImagePanelTextInput = forwardRef<
- HTMLInputElement,
- Omit, "size">
->((props, ref) => (
-
-));
diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx
index 483dc88ec3..029e42b1e7 100644
--- a/packages/react/src/editor/BlockNoteView.tsx
+++ b/packages/react/src/editor/BlockNoteView.tsx
@@ -35,6 +35,7 @@ import {
import { ComponentsContext } from "./ComponentsContext";
import { EditorContent } from "./EditorContent";
import "./styles.css";
+import { ariakitComponents } from "../ariakit/components";
const mantineTheme = {
// Removes button press effect
@@ -232,7 +233,7 @@ export const BlockNoteViewAriakit = (
props: ComponentProps
) => {
return (
-
+
);
diff --git a/packages/react/src/editor/ComponentsContext.ts b/packages/react/src/editor/ComponentsContext.ts
index 9506a9cd11..6fa1fc08cc 100644
--- a/packages/react/src/editor/ComponentsContext.ts
+++ b/packages/react/src/editor/ComponentsContext.ts
@@ -1,5 +1,6 @@
import {
ComponentProps,
+ ComponentPropsWithoutRef,
ComponentType,
createContext,
useContext,
@@ -47,6 +48,34 @@ export type MenuTriggerProps = {
sub?: boolean;
};
+export type PanelProps = {
+ defaultOpenTab: string;
+ openTab: string;
+ setOpenTab: (name: string) => void;
+ tabs: {
+ name: string;
+ tabPanel: React.ReactNode;
+ }[];
+ loading: boolean;
+ setLoading: (loading: boolean) => void;
+};
+
+export type PanelButtonProps = Omit, "size">;
+
+export type PanelFileInputProps = {
+ placeholder?: string;
+ value?: File | null;
+ defaultValue?: File | null;
+ onChange?: (payload: File | null) => void;
+};
+
+export type PanelTabProps = ComponentPropsWithoutRef<"div">;
+
+export type PanelTextInputProps = Omit<
+ ComponentPropsWithoutRef<"input">,
+ "size"
+>;
+
export type SuggestionMenuItemProps = {
title: string;
onClick: () => void;
@@ -82,6 +111,7 @@ export type ToolbarSelectProps = {
};
export type ComponentsContextValue = {
+ FileInput: any;
Form: ComponentType<{
children: React.ReactNode;
}>;
@@ -99,6 +129,11 @@ export type ComponentsContextValue = {
children: React.ReactNode;
}>;
MenuItem: ComponentType;
+ Panel: ComponentType;
+ PanelButton: ComponentType;
+ PanelFileInput: ComponentType;
+ PanelTab: ComponentType;
+ PanelTextInput: ComponentType;
Popover: any;
PopoverTrigger: any;
PopoverContent: any;
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 5174fdb85c..13b5c1716d 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -50,13 +50,9 @@ export * from "./components/SuggestionMenu/types";
export * from "./components/ImagePanel/ImagePanelController";
export * from "./components/ImagePanel/ImagePanelProps";
-export * from "./components/ImagePanel/mantine/DefaultTabs/EmbedTab";
-export * from "./components/ImagePanel/mantine/DefaultTabs/UploadTab";
-export * from "./components/ImagePanel/mantine/ImagePanel";
-export * from "./components/ImagePanel/mantine/ImagePanelButton";
-export * from "./components/ImagePanel/mantine/ImagePanelFileInput";
-export * from "./components/ImagePanel/mantine/ImagePanelTab";
-export * from "./components/ImagePanel/mantine/ImagePanelTextInput";
+export * from "./components/ImagePanel/DefaultTabs/EmbedTab";
+export * from "./components/ImagePanel/DefaultTabs/UploadTab";
+export * from "./components/ImagePanel/ImagePanel";
export * from "./components/TableHandles/TableHandle";
export * from "./components/TableHandles/TableHandleProps";
diff --git a/packages/react/src/mantine/mantineComponents.tsx b/packages/react/src/mantine/mantineComponents.tsx
index c8dfa49add..1f9e80504d 100644
--- a/packages/react/src/mantine/mantineComponents.tsx
+++ b/packages/react/src/mantine/mantineComponents.tsx
@@ -9,6 +9,11 @@ import {
MenuLabel,
MenuTrigger,
} from "./menu/Menu";
+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 } from "./popover/Popover";
import { Toolbar } from "./toolbar/Toolbar";
import { ToolbarButton } from "./toolbar/ToolbarButton";
@@ -24,6 +29,11 @@ export const mantineComponents: ComponentsContextValue = {
MenuDivider,
MenuLabel,
MenuItem,
+ Panel,
+ PanelButton,
+ PanelFileInput,
+ PanelTab,
+ PanelTextInput,
Popover: Popover,
PopoverContent: PopoverDropdown,
PopoverTrigger: PopoverTarget,
diff --git a/packages/react/src/mantine/panel/Panel.tsx b/packages/react/src/mantine/panel/Panel.tsx
new file mode 100644
index 0000000000..7e2110dffc
--- /dev/null
+++ b/packages/react/src/mantine/panel/Panel.tsx
@@ -0,0 +1,27 @@
+import { Group, LoadingOverlay, Tabs } from "@mantine/core";
+
+import { PanelProps } from "../../editor/ComponentsContext";
+
+export const Panel = (props: PanelProps) => {
+ return (
+
+
+ {props.loading && }
+
+
+ {props.tabs.map((tab) => (
+
+ {tab.name}
+
+ ))}
+
+
+ {props.tabs.map((tab) => (
+
+ {tab.tabPanel}
+
+ ))}
+
+
+ );
+};
diff --git a/packages/react/src/mantine/panel/PanelButton.tsx b/packages/react/src/mantine/panel/PanelButton.tsx
new file mode 100644
index 0000000000..0e82cf04c5
--- /dev/null
+++ b/packages/react/src/mantine/panel/PanelButton.tsx
@@ -0,0 +1,13 @@
+import { Button } from "@mantine/core";
+
+import { PanelButtonProps } from "../../editor/ComponentsContext";
+
+export const PanelButton = (props: PanelButtonProps) => {
+ const { children, ...rest } = props;
+
+ return (
+
+ );
+};
diff --git a/packages/react/src/mantine/panel/PanelFileInput.tsx b/packages/react/src/mantine/panel/PanelFileInput.tsx
new file mode 100644
index 0000000000..093ad6ee51
--- /dev/null
+++ b/packages/react/src/mantine/panel/PanelFileInput.tsx
@@ -0,0 +1,7 @@
+import { FileInput } from "@mantine/core";
+
+import { PanelFileInputProps } from "../../editor/ComponentsContext";
+
+export const PanelFileInput = (props: PanelFileInputProps) => (
+
+);
diff --git a/packages/react/src/mantine/panel/PanelTab.tsx b/packages/react/src/mantine/panel/PanelTab.tsx
new file mode 100644
index 0000000000..dfb92e3fa9
--- /dev/null
+++ b/packages/react/src/mantine/panel/PanelTab.tsx
@@ -0,0 +1,15 @@
+import { mergeCSSClasses } from "@blocknote/core";
+
+import { PanelTabProps } from "../../editor/ComponentsContext";
+
+export const PanelTab = (props: PanelTabProps) => {
+ const { className, children, ...rest } = props;
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/react/src/mantine/panel/PanelTextInput.tsx b/packages/react/src/mantine/panel/PanelTextInput.tsx
new file mode 100644
index 0000000000..7b5ee14284
--- /dev/null
+++ b/packages/react/src/mantine/panel/PanelTextInput.tsx
@@ -0,0 +1,9 @@
+import { TextInput } from "@mantine/core";
+
+import { PanelTextInputProps } from "../../editor/ComponentsContext";
+
+export const PanelTextInput = (props: PanelTextInputProps) => {
+ const { children, ...rest } = props;
+
+ return ;
+};
diff --git a/packages/shadcn/package.json b/packages/shadcn/package.json
index 21c9a0d739..f435c6a968 100644
--- a/packages/shadcn/package.json
+++ b/packages/shadcn/package.json
@@ -55,6 +55,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"class-variance-authority": "^0.7.0",
diff --git a/packages/shadcn/src/components/ui/input.tsx b/packages/shadcn/src/components/ui/input.tsx
new file mode 100644
index 0000000000..1afb83e15a
--- /dev/null
+++ b/packages/shadcn/src/components/ui/input.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+
+import { cn } from "../../lib/utils";
+
+export type InputProps = React.InputHTMLAttributes;
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+Input.displayName = "Input";
+
+export { Input };
diff --git a/packages/shadcn/src/components/ui/tabs.tsx b/packages/shadcn/src/components/ui/tabs.tsx
new file mode 100644
index 0000000000..b456cf653c
--- /dev/null
+++ b/packages/shadcn/src/components/ui/tabs.tsx
@@ -0,0 +1,53 @@
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+
+import { cn } from "../../lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/packages/shadcn/src/index.tsx b/packages/shadcn/src/index.tsx
index 1c9054dd1f..c6cbfbd9aa 100644
--- a/packages/shadcn/src/index.tsx
+++ b/packages/shadcn/src/index.tsx
@@ -12,6 +12,11 @@ import {
MenuLabel,
MenuTrigger,
} from "./menu/Menu";
+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 "./style.css";
import { Toolbar, ToolbarButton, ToolbarSelect } from "./toolbar/Toolbar";
@@ -25,6 +30,11 @@ export const components: ComponentsContextValue = {
MenuLabel,
MenuDivider,
MenuItem,
+ Panel,
+ PanelButton,
+ PanelFileInput,
+ PanelTab,
+ PanelTextInput,
Popover: () => null,
PopoverContent: () => null,
PopoverTrigger: () => null,
diff --git a/packages/shadcn/src/panel/Panel.tsx b/packages/shadcn/src/panel/Panel.tsx
new file mode 100644
index 0000000000..6f264459b5
--- /dev/null
+++ b/packages/shadcn/src/panel/Panel.tsx
@@ -0,0 +1,36 @@
+import { PanelProps } from "../../../react/src";
+
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "../components/ui/tabs";
+
+export const Panel = (props: PanelProps) => {
+ return (
+
+ {/*{props.loading && }*/}
+
+
+ {props.tabs.map((tab) => (
+
+ {tab.name}
+
+ ))}
+
+
+ {props.tabs.map((tab) => (
+
+ {tab.tabPanel}
+
+ ))}
+
+ );
+};
diff --git a/packages/shadcn/src/panel/PanelButton.tsx b/packages/shadcn/src/panel/PanelButton.tsx
new file mode 100644
index 0000000000..2341b45f13
--- /dev/null
+++ b/packages/shadcn/src/panel/PanelButton.tsx
@@ -0,0 +1,13 @@
+import { PanelButtonProps } from "../../../react/src";
+import { Button } from "../components/ui/button";
+import { cn } from "../lib/utils";
+
+export const PanelButton = (props: PanelButtonProps) => {
+ const { children, className, ...rest } = props;
+
+ return (
+
+ );
+};
diff --git a/packages/shadcn/src/panel/PanelFileInput.tsx b/packages/shadcn/src/panel/PanelFileInput.tsx
new file mode 100644
index 0000000000..18df11234e
--- /dev/null
+++ b/packages/shadcn/src/panel/PanelFileInput.tsx
@@ -0,0 +1,14 @@
+import { PanelFileInputProps } from "../../../react/src";
+import { Input } from "../components/ui/input";
+
+export const PanelFileInput = (props: PanelFileInputProps) => {
+ return (
+ props.onChange?.(e.target.files![0])}
+ placeholder={props.placeholder}
+ />
+ );
+};
diff --git a/packages/shadcn/src/panel/PanelTab.tsx b/packages/shadcn/src/panel/PanelTab.tsx
new file mode 100644
index 0000000000..238ae28786
--- /dev/null
+++ b/packages/shadcn/src/panel/PanelTab.tsx
@@ -0,0 +1,15 @@
+import { mergeCSSClasses } from "@blocknote/core";
+
+import { PanelTabProps } from "../../../react/src";
+
+export const PanelTab = (props: PanelTabProps) => {
+ const { className, children, ...rest } = props;
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/shadcn/src/panel/PanelTextInput.tsx b/packages/shadcn/src/panel/PanelTextInput.tsx
new file mode 100644
index 0000000000..897333dcef
--- /dev/null
+++ b/packages/shadcn/src/panel/PanelTextInput.tsx
@@ -0,0 +1,12 @@
+import { PanelTextInputProps } from "../../../react/src";
+import { Input } from "../components/ui/input";
+
+export const PanelTextInput = (props: PanelTextInputProps) => {
+ const { children, ...rest } = props;
+
+ return (
+
+ {children}
+
+ );
+};