editor
editor copied to clipboard
Go headless and use shadcn for internal UI Components
ShadCN UI is a collection of pre-built, customizable UI components for React, built on Radix UI and styled with Tailwind CSS. Unlike traditional component libraries, ShadCN UI installs components directly into your project as local files, allowing full customization without external dependencies. To add a component, you just run (p)npx shadcn-ui@latest add component-name, which copies the component's code into your project, making it easy to modify and style as needed. This approach provides flexibility, maintainability, and seamless integration with whatever design system the user may want to use.
While the shadcn ui default components just import radix and use tailwind class names, the user is not limited to those, and can switch to whatever design system. One does not even need to add it as a dependency. It can just be invoked by npm cli. It just needs a components.json file in users project, and no additional dependencies.
I've been using it for a while, and I have to say that it is really pleasant to use in React projects.
Some other libraries providing shadcn components are:
- Plate.js
- React Flow
- https://v0.dev/
I've seen that in a bunch of other issues, it was mentioned that MDXEditor bundles some sort of micro UI framework. This makes it also a bit convoluted to use any "external" component, for dialogs, toolbar, etc.
What I'm proposing, is to have a MDX Editor registry of components that can be easily passed to the plugins, and they receive access to the gurx state or editor via props. e.g.
We have already
toolbarPlugin({ toolbarContents: () => <KitchenSinkToolbar /> })
We could move to something like
toolbarPlugin({ toolbarContents: (props) => <KitchenSinkToolbar {...props} /> })
Where props contain a reference to the editor, and the KitchenSinkToolbar is provided as a shadcn component
Unless I am missing something, there's nothing stopping people from re-implementing the toolbar using ShadCN, or any other UI framework of choice. You don't need props to access the state management, you do this through the documented hooks and cells/signals. Take a look at the bold/italic/underline toolbar component for example:
https://github.com/mdx-editor/editor/blob/main/src/plugins/toolbar/components/BoldItalicUnderlineToggles.tsx#L19-L20
To make it clear, I totally believe that "bring your own UI" is the way to go for serious integrations = that's the reason why the toolbar itself is packaged as a plugin. But, unless there's an out of the box toolbar that showcases the capabilities the barrier to entry for the component adoption becomes quite high.
Let me know if this makes sense.
It makes sense. What I haven't understood yet, considering I have multiple editors on a page, is how cells and signals in gurx, actually refer to the current editor, if I'm not passing a reference to the rootEditor$ via props or some state management hook.
For example
const [currentFormat, iconComponentFor] = useCellValues(currentFormat$, iconComponentFor$)
const applyFormat = usePublisher(applyFormat$)
And in the index:
export const currentFormat$ = Cell(0 as FORMAT)
That would be illuminating. I can then attempt to set up an example of components with shadcn to post here, and see if it is worth the effort, not to package them in the @mdxeditor/editor repo, but in a custom shadcn component registry, and then add a PR to the docs showcasing how to actually do this.
Does rootEditor$ cell always hold an instance to the active (selected) editor?
I'm taking this from Gurx README:
The cells, signals, and actions are just blueprints and references to nodes in a realm. The actual instantiation and interaction (publishing, subscribing, etc.) happens through a Realm instance. A realm is initially empty; it creates its node instances when you subscribe or publish to a cell/signal through the realm's methods. If a cell/signal refers to other nodes in its initialization function, the realm will automatically recursively include those nodes as well.
Multiple editor instances have multiple corresponding realm instances (held in a context). When you refer to a cell, it's actually reaching into the current realm.
Hope this makes sense. Jotai has a similar concept as far as I know.
Interesting approach! Thanks
Regarding the main topic of the issue
that's the reason why the toolbar itself is packaged as a plugin
Shadcn could actually be useful in plugin management. While the core package could include the plugin-less editor, shadcn can (and should in shadcn libraries) also handle simple lib files and hooks.
Not only the UI, but also the plugin logic could be downloadable separately, and customized easily in users' project.
If someone wants to use the toolbar, can run npx shadcn@latest add @mdxeditor/plugins/toolbar and it will download components/ui/mdx-toolbar.tsx and lib/mdx-plugins/toolbar.ts.
The "bring your own UI" is then achieved by the user customizing those 2 files
Thanks for sharing that. I believe that shadcn works great for "the frontend" of the frontend, where the pixel pushing happens. It does offload the actual hard parts to Radix, which I would not eject. Back to your example, I can see how the toolbar UI can be a shadcn plugin, but I would not do the same for the state management (if this is what you refer to with `lib/mdx-plugins/toolbar.ts).
Notice that ejecting the state management code is actually not necessary for it to be extended, unless you really need to hack and slash into it (which means that most likely you need a different plugin). You can easily wire up new cells and signals to the existing ones. The ability to do that was the main reason I came up with the gurx architecture.
Interesting topic. I understand we have some flexibility with the toolbar, but what about integrating another UI framework to the editor content?
It seems that the content styling is only possible via CSS, which works fine if your doing something from scratch, but might not be ideal for an existing project (projects using styled components, for example)
Example:
- I'm using @mui/material components with a custom theme
- I would like my editor content to render this:
paragraph node-><Typography variant="body2">...</Typography>. (Typographyis a @mui/material component that renders a styled text based on a theme)
@rodrigovallades point 2 is very unlikely to happen, as this will have to replace all plain element nodes in Lexical with custom React components.
Fully externalizing the "Chrome" is a good idea, though.