-
-
Notifications
You must be signed in to change notification settings - Fork 708
feat: Mobile-optimized formatting toolbar #1284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
6073c3e
fix: Import only from a single Shiki bundle
areknawo 4369092
fix: Select options styling on Chrome, Linux
areknawo 3a390a6
fix: Detect proper language ID when parsing from HTML
areknawo 1e0003d
fix: Support "typescriptreact" alias for tsx
areknawo ef27d82
Merge branch 'main' into areknawo-main
matthewlipski 31d0f87
minor code change
YousefED fc608ff
remove setting cursor position when inserting code block
YousefED fe62af7
Merge branch 'TypeCellOS:main' into main
areknawo a462eae
experiment: Visual Viewport API
areknawo 4e57e68
experiment: Directly setting transform
areknawo 3071f09
feat: Experimental mobile formatting toolbar controller
areknawo 1c0dd2d
feat: Optimize formatting toolbar controller for mobile
areknawo b3d4af4
Merge branch 'TypeCellOS:main' into main
areknawo 6dfdc1f
fix: Move overflow-x to bn-toolbar
areknawo fc3b3a6
chore: Describe the experimental mobile formatting toolbar controller
areknawo f0838da
chore: Add an example for the experimental mobile formatting toolbar …
areknawo 2af8966
fix: Experimental toolbar positioning
areknawo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/.bnexample.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "playground": true, | ||
| "docs": true, | ||
| "author": "areknawo", | ||
| "tags": [ | ||
| "Intermediate", | ||
| "UI Components", | ||
| "Formatting Toolbar", | ||
| "Appearance & Styling" | ||
| ] | ||
| } |
42 changes: 42 additions & 0 deletions
42
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/App.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import "@blocknote/core/fonts/inter.css"; | ||
| import { | ||
| ExperimentalMobileFormattingToolbarController, | ||
| useCreateBlockNote, | ||
| } from "@blocknote/react"; | ||
| import { BlockNoteView } from "@blocknote/mantine"; | ||
| import "@blocknote/mantine/style.css"; | ||
|
|
||
| import "./style.css"; | ||
|
|
||
| export default function App() { | ||
| // Creates a new editor instance. | ||
| const editor = useCreateBlockNote({ | ||
| initialContent: [ | ||
| { | ||
| type: "paragraph", | ||
| content: "Welcome to this demo!", | ||
| }, | ||
| { | ||
| type: "paragraph", | ||
| content: | ||
| "Check out the experimental mobile formatting toolbar by selecting some text (best experienced on a mobile device).", | ||
| }, | ||
| { | ||
| type: "paragraph", | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| // Renders the editor instance using a React component. | ||
| return ( | ||
| // Disables the default formatting toolbar and re-adds it without the | ||
| // `FormattingToolbarController` component. You may have seen | ||
| // `FormattingToolbarController` used in other examples, but we omit it here | ||
| // as we want to control the position and visibility ourselves. BlockNote | ||
| // also uses the `FormattingToolbarController` when displaying the | ||
| // Formatting Toolbar by default. | ||
| <BlockNoteView editor={editor} formattingToolbar={false}> | ||
| <ExperimentalMobileFormattingToolbarController /> | ||
| </BlockNoteView> | ||
| ); | ||
| } |
10 changes: 10 additions & 0 deletions
10
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # Experimental Mobile Formatting Toolbar | ||
|
|
||
| This example shows how to use the experimental mobile formatting toolbar, which uses [Visual Viewport API](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API) to position the toolbar right above the virtual keyboard on mobile devices. | ||
|
|
||
| Controller is currently marked **experimental** due to the flickering issue with positioning (caused by delays of the Visual Viewport API) | ||
|
|
||
| **Relevant Docs:** | ||
|
|
||
| - [Changing the Formatting Toolbar](/docs/ui-components/formatting-toolbar#changing-the-formatting-toolbar) | ||
| - [Editor Setup](/docs/editor-basics/setup) | ||
14 changes: 14 additions & 0 deletions
14
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/index.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <html lang="en"> | ||
| <head> | ||
| <script> | ||
| <!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY --> | ||
| </script> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Experimental Mobile Formatting Toolbar</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="./main.tsx"></script> | ||
| </body> | ||
| </html> |
11 changes: 11 additions & 0 deletions
11
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/main.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY | ||
| import React from "react"; | ||
| import { createRoot } from "react-dom/client"; | ||
| import App from "./App"; | ||
|
|
||
| const root = createRoot(document.getElementById("root")!); | ||
| root.render( | ||
| <React.StrictMode> | ||
| <App /> | ||
| </React.StrictMode> | ||
| ); |
37 changes: 37 additions & 0 deletions
37
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| { | ||
| "name": "@blocknote/example-experimental-mobile-formatting-toolbar", | ||
| "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", | ||
| "private": true, | ||
| "version": "0.12.4", | ||
| "scripts": { | ||
| "start": "vite", | ||
| "dev": "vite", | ||
| "build": "tsc && vite build", | ||
| "preview": "vite preview", | ||
| "lint": "eslint . --max-warnings 0" | ||
| }, | ||
| "dependencies": { | ||
| "@blocknote/core": "latest", | ||
| "@blocknote/react": "latest", | ||
| "@blocknote/ariakit": "latest", | ||
| "@blocknote/mantine": "latest", | ||
| "@blocknote/shadcn": "latest", | ||
| "react": "^18.3.1", | ||
| "react-dom": "^18.3.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/react": "^18.0.25", | ||
| "@types/react-dom": "^18.0.9", | ||
| "@vitejs/plugin-react": "^4.3.1", | ||
| "eslint": "^8.10.0", | ||
| "vite": "^5.3.4" | ||
| }, | ||
| "eslintConfig": { | ||
| "extends": [ | ||
| "../../../.eslintrc.js" | ||
| ] | ||
| }, | ||
| "eslintIgnore": [ | ||
| "dist" | ||
| ] | ||
| } |
9 changes: 9 additions & 0 deletions
9
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/style.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| .bn-container { | ||
| display: flex; | ||
| flex-direction: column-reverse; | ||
| gap: 8px; | ||
| } | ||
|
|
||
| .bn-formatting-toolbar { | ||
| margin-inline: auto; | ||
| } |
36 changes: 36 additions & 0 deletions
36
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/tsconfig.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| { | ||
| "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", | ||
| "compilerOptions": { | ||
| "target": "ESNext", | ||
| "useDefineForClassFields": true, | ||
| "lib": [ | ||
| "DOM", | ||
| "DOM.Iterable", | ||
| "ESNext" | ||
| ], | ||
| "allowJs": false, | ||
| "skipLibCheck": true, | ||
| "esModuleInterop": false, | ||
| "allowSyntheticDefaultImports": true, | ||
| "strict": true, | ||
| "forceConsistentCasingInFileNames": true, | ||
| "module": "ESNext", | ||
| "moduleResolution": "Node", | ||
| "resolveJsonModule": true, | ||
| "isolatedModules": true, | ||
| "noEmit": true, | ||
| "jsx": "react-jsx", | ||
| "composite": true | ||
| }, | ||
| "include": [ | ||
| "." | ||
| ], | ||
| "__ADD_FOR_LOCAL_DEV_references": [ | ||
| { | ||
| "path": "../../../packages/core/" | ||
| }, | ||
| { | ||
| "path": "../../../packages/react/" | ||
| } | ||
| ] | ||
| } |
32 changes: 32 additions & 0 deletions
32
examples/03-ui-components/14-experimental-mobile-formatting-toolbar/vite.config.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY | ||
| import react from "@vitejs/plugin-react"; | ||
| import * as fs from "fs"; | ||
| import * as path from "path"; | ||
| import { defineConfig } from "vite"; | ||
| // import eslintPlugin from "vite-plugin-eslint"; | ||
| // https://vitejs.dev/config/ | ||
| export default defineConfig((conf) => ({ | ||
| plugins: [react()], | ||
| optimizeDeps: {}, | ||
| build: { | ||
| sourcemap: true, | ||
| }, | ||
| resolve: { | ||
| alias: | ||
| conf.command === "build" || | ||
| !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) | ||
| ? {} | ||
| : ({ | ||
| // Comment out the lines below to load a built version of blocknote | ||
| // or, keep as is to load live from sources with live reload working | ||
| "@blocknote/core": path.resolve( | ||
| __dirname, | ||
| "../../packages/core/src/" | ||
| ), | ||
| "@blocknote/react": path.resolve( | ||
| __dirname, | ||
| "../../packages/react/src/" | ||
| ), | ||
| } as any), | ||
| }, | ||
| })); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
.../react/src/components/FormattingToolbar/ExperimentalMobileFormattingToolbarController.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import { BlockSchema, InlineContentSchema, StyleSchema } from "@blocknote/core"; | ||
| import { UseFloatingOptions } from "@floating-ui/react"; | ||
| import { FC, CSSProperties, useMemo, useRef, useState, useEffect } from "react"; | ||
| import { useBlockNoteEditor } from "../../hooks/useBlockNoteEditor.js"; | ||
| import { useUIPluginState } from "../../hooks/useUIPluginState.js"; | ||
| import { FormattingToolbar } from "./FormattingToolbar.js"; | ||
| import { FormattingToolbarProps } from "./FormattingToolbarProps.js"; | ||
|
|
||
| /** | ||
| * Experimental formatting toolbar controller for mobile devices. | ||
| * Uses Visual Viewport API to position the toolbar above the virtual keyboard. | ||
| * | ||
| * Currently marked experimental due to the flickering issue with positioning cause by the use of the API (and likely a delay in its updates). | ||
| */ | ||
| export const ExperimentalMobileFormattingToolbarController = (props: { | ||
areknawo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| formattingToolbar?: FC<FormattingToolbarProps>; | ||
| floatingOptions?: Partial<UseFloatingOptions>; | ||
| }) => { | ||
| const [transform, setTransform] = useState<string>("none"); | ||
| const divRef = useRef<HTMLDivElement>(null); | ||
| const editor = useBlockNoteEditor< | ||
| BlockSchema, | ||
| InlineContentSchema, | ||
| StyleSchema | ||
| >(); | ||
| const state = useUIPluginState( | ||
| editor.formattingToolbar.onUpdate.bind(editor.formattingToolbar) | ||
| ); | ||
| const style = useMemo<CSSProperties>(() => { | ||
| return { | ||
| display: "flex", | ||
| position: "fixed", | ||
| bottom: 0, | ||
| zIndex: 3000, | ||
| transform, | ||
| }; | ||
| }, [transform]); | ||
|
|
||
| useEffect(() => { | ||
| const viewport = window.visualViewport!; | ||
| function viewportHandler() { | ||
| // Calculate the offset necessary to set the toolbar above the virtual keyboard (using the offset info from the visualViewport) | ||
| const layoutViewport = document.body; | ||
| const offsetLeft = viewport.offsetLeft; | ||
| const offsetTop = | ||
| viewport.height - | ||
| layoutViewport.getBoundingClientRect().height + | ||
| viewport.offsetTop; | ||
|
|
||
| setTransform( | ||
| `translate(${offsetLeft}px, ${offsetTop}px) scale(${ | ||
| 1 / viewport.scale | ||
| })` | ||
| ); | ||
| } | ||
| window.visualViewport!.addEventListener("scroll", viewportHandler); | ||
| window.visualViewport!.addEventListener("resize", viewportHandler); | ||
| viewportHandler(); | ||
|
|
||
| return () => { | ||
| window.visualViewport!.removeEventListener("scroll", viewportHandler); | ||
| window.visualViewport!.removeEventListener("resize", viewportHandler); | ||
| }; | ||
| }, []); | ||
|
|
||
| if (!state) { | ||
| return null; | ||
| } | ||
|
|
||
| if (!state.show && divRef.current) { | ||
| // The component is fading out. Use the previous state to render the toolbar with innerHTML, | ||
| // because otherwise the toolbar will quickly flickr (i.e.: show a different state) while fading out, | ||
| // which looks weird | ||
| return ( | ||
| <div | ||
| ref={divRef} | ||
| style={style} | ||
| dangerouslySetInnerHTML={{ __html: divRef.current.innerHTML }}></div> | ||
| ); | ||
| } | ||
|
|
||
| const Component = props.formattingToolbar || FormattingToolbar; | ||
|
|
||
| return ( | ||
| <div ref={divRef} style={style}> | ||
| <Component /> | ||
| </div> | ||
| ); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When is the flickering visible? I don't think I saw it when playing around in the preview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you test on mobile? When you scroll / down a large document the updating of the position is a bit delayed