-
-
Notifications
You must be signed in to change notification settings - Fork 565
feat: Toggle blocks #1707
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
feat: Toggle blocks #1707
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/mantine
@blocknote/react
@blocknote/server-util
@blocknote/shadcn
@blocknote/xl-ai
@blocknote/xl-docx-exporter
@blocknote/xl-multi-column
@blocknote/xl-odt-exporter
@blocknote/xl-pdf-exporter
commit: |
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.
What I'm more interested in seeing here is how we can add this to existing block types (such as headings with a prop) rather than as a separate element like this implements.
packages/core/src/blocks/ToggleHeadingBlockContent/ToggleHeadingBlockContent.ts
Outdated
Show resolved
Hide resolved
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.
Does isToggleable have to show up on every element now? Can it not just be assumed false if undefined? Or, do we always render "default values"?
packages/core/src/blocks/HeadingBlockContent/HeadingBlockContent.ts
Outdated
Show resolved
Hide resolved
{ | ||
onItemClick: () => { | ||
insertOrUpdateBlock(editor, { | ||
type: "heading", | ||
props: { level: 1, isToggleable: true }, | ||
}); | ||
}, | ||
key: "toggle_heading", | ||
...editor.dictionary.slash_menu.toggle_heading, | ||
}, | ||
{ |
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.
Is there a way to disable toggleable headings but not headings?
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.
I think so, you could just do getDefaultSlashMenuItems(...).filter((item) => !item.key.startsWith("toggle_heading"))
right? So pretty much the same way you'd filter out any other items from the menu. Or do you mean in the schema itself?
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.
I meant from the schema
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.
Ah, in that case not really but I think it's ok for now, seems like something that will get fixed either way when converting default blocks to use the custom blocks API.
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.
Awesome, almost there I think!
empty toggle list should have a placeholder "Toggle" (same as Notion)
I don't think this works?
Others:
- let's make the font color of the "add button" placeholder more placeholder-like (gray?)
- Is it easy to remove the vertical lines for toggled blocks? imo this still looks a bit weird
One architectural question:
I realize we're currently following notion in this design, but let's make sure having an is_toggleable prop on a heading makes more sense than having a separate ToggleHeading block. What are the pros / cons? (cc @nperez0111 )
|
||
export const defaultToggledState: ToggledState = { | ||
set: (block, isToggled: boolean) => | ||
window.localStorage.setItem( |
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.
should we move this logic (whether to store to local storage) to be configurable on the editor? Or want to keep it as part of the block spec (and thus the schema)?
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.
cc @nperez0111
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.
imo, for now, part of the block spec (lowest-level possible).
In the future, this would be a "bundle" that you can configure independently
...defaultProps, | ||
} satisfies PropSchema; | ||
|
||
const ToggleListItemBlockContent = createStronglyTypedTiptapNode({ |
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.
Food for thought; when streamlining built-in blocks, I'm curious if we can come up with a light-weight way to make this more readable (React / jsx like), without pulling in all of react. And thus, get rid of the duplicate (vanilla + react) implementation
cc @nperez0111
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.
Yes, I have ideas for this. Basically use jsx for actual document.createElement calls, without all of React. In my ideal, you wouldn't even need React.
@@ -204,9 +206,11 @@ NESTED BLOCKS | |||
} | |||
|
|||
/* Unordered */ | |||
.bn-block-content[data-content-type="bulletListItem"] { | |||
.bn-block-content[data-content-type="bulletListItem"]::before { |
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.
can you explain why this changed? why the before element that wasn't here earlier?
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.
Ah I forgot to mention this in the PR description - I adjusted offsets for list items, indented blocks, and the "add block" button of the toggle blocks to make them all the same. So all of these are now vertically in-line now, and these styles are a part of that.
My argument for this was that let's say you were generating the table of contents for a page, would you consider a heading that happens to be toggle-able, not a heading? By making it a different block, you change the semantics of what it is, whereas toggling is purely a visual thing (much like setting a background color wouldn't change the type of block it is) |
Good point. Going to challenge a bit more to make sure we're confident (I still don't think I have a preference, or maybe start to prefer the current implementation):
|
Good point, I'd argue that these should actually be named UI-wise, the naming could be different for understanding maybe, but, I think, they are functionally equivalent. List does sort of convey that hitting enter, you'd get more of them. List items are actually the perfect demonstration of what I mean here about semantics, it is always an
I think what would solve this is the ability to easily customize the schema. In this case, I'd want an option to remove |
To follow up with the naming convention for Also r.e. the |
packages/core/src/i18n/locales/nl.ts
Outdated
@@ -157,7 +157,7 @@ export const nl: Dictionary = { | |||
placeholders: { | |||
default: "Voer tekst in of type '/' voor commando's", | |||
heading: "Kop", | |||
toggleList: "Schakelaar", | |||
toggleListItem: "Schakelaar", |
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.
This word means "switch" (as in, "light switch" / "light toggle").
Can we give your AI some extra context to improve the translations?
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.
Hmm, I'm just using Copilot built into VSCode so it should at least have the context of the english translation file, any way I can add to this?
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.
I think you can @ mention specific files in the chat? It also helps to just chat explanation of what the toggle is (i.e.: a block that expands in a document editor)
@nperez0111 that's also the case. I'm starting to think that the current naming is ok - unless anyone feels otherwise after the above discussion |
I think the current naming is okay if it keeps the newline behavior. If someone wants to disable that, then it should be called a toggle block. But, obviously no way for us to enforce it. Agreed, naming is fine here. |
Thought about it, & decided it was fine like this:
|
Toggle wrapper element/component
This PR adds a new component,
ToggleWrapper
, as well as a vanilla HTML element implementation of it,createToggleWrapper
. These can be used to wrap existing blocks, or create new ones and allow the user to toggle showing/hiding their children.The toggle wrapper renders a button to the left of the block to show/hide the children. Additionally, if children are shown but none exist, a button is rendered to prompt the user to add a block. Because the toggled state is only in the view, toggling the visibility of children only happens client-side when using collaboration.
A
toggledState
object can also be passed to the toggle wrapper, which includes aget
andset
function to be able to preserve the state on re-render and reload. By default, this is done using local storage.Schema changes
This PR also adds toggle headings and toggle list items to the default editor schema.
The toggle headings are implemented by adding an
isToggleable
prop, and rendering the block in a toggle wrapper when this is set totrue
.The toggle list item is a new, separate block, which always renders a paragraph in a toggle wrapper. Like the other list items though, hitting Enter in one creates a new block of the same type, which is why I made it a separate block rather than adding
isToggleable
to the existing paragraph or list item blocks. Additionally, the block is exported differently to formats like docx, as the content gets prepended with a ">".TODO
Currently some
xl-ai
tests are failing, and these have been temporarily disabled.Closes #196
Closes #549
Closes #910