├── .gitignore ├── .prettierignore ├── README.md ├── app ├── (app) │ ├── (root) │ │ ├── page.tsx │ │ └── section.tsx │ ├── blocks │ │ ├── [...categories] │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── page.tsx │ ├── docs │ │ ├── [[...slug]] │ │ │ └── page.tsx │ │ └── layout.tsx │ └── layout.tsx ├── (view) │ └── view │ │ └── [name] │ │ └── page.tsx ├── layout.tsx └── og │ ├── geist-regular-otf.json │ ├── geist-semibold-otf.json │ ├── geistmono-regular-otf.json │ └── route.tsx ├── components.json ├── components ├── active-theme.tsx ├── analytics.tsx ├── announcement.tsx ├── block-display.tsx ├── block-image.tsx ├── block-viewer.tsx ├── blocks-nav.tsx ├── blocks │ └── editor │ │ ├── editor.tsx │ │ ├── nodes.ts │ │ └── plugins.tsx ├── callout.tsx ├── cards │ ├── activity-goal.tsx │ ├── calendar.tsx │ ├── chat.tsx │ ├── cookie-settings.tsx │ ├── create-account.tsx │ ├── exercise-minutes.tsx │ ├── forms.tsx │ ├── index.tsx │ ├── payment-method.tsx │ ├── payments.tsx │ ├── report-issue.tsx │ ├── share.tsx │ ├── stats.tsx │ └── team-members.tsx ├── chart-code-viewer.tsx ├── chart-copy-button.tsx ├── chart-display.tsx ├── chart-toolbar.tsx ├── charts-nav.tsx ├── code-block-command.tsx ├── code-collapsible-wrapper.tsx ├── code-tabs.tsx ├── color-format-selector.tsx ├── color-palette.tsx ├── color.tsx ├── colors-nav.tsx ├── command-menu.tsx ├── component-preview-tabs.tsx ├── component-preview.tsx ├── component-source.tsx ├── component-wrapper.tsx ├── components-list.tsx ├── copy-button.tsx ├── docs-breadcrumb.tsx ├── docs-copy-page.tsx ├── docs-sidebar.tsx ├── docs-toc.tsx ├── examples-nav.tsx ├── github-link.tsx ├── icons.tsx ├── lo-fi │ ├── accordion.tsx │ ├── alert.tsx │ ├── atom.tsx │ ├── component.tsx │ └── index.tsx ├── main-nav.tsx ├── mobile-nav.tsx ├── mode-switcher.tsx ├── nav-header.tsx ├── nav-user.tsx ├── open-in-v0-button.tsx ├── open-in-v0-cta.tsx ├── page-header.tsx ├── page-nav.tsx ├── site-footer.tsx ├── site-header.tsx ├── tailwind-indicator.tsx ├── theme-customizer.tsx ├── theme-provider.tsx └── theme-selector.tsx ├── content └── docs │ ├── (root) │ ├── index.mdx │ ├── installation.mdx │ └── meta.json │ ├── meta.json │ └── plugins │ ├── actions.mdx │ ├── actions │ ├── clear-editor.mdx │ ├── counter-character.mdx │ ├── edit-mode-toggle.mdx │ ├── import-export.mdx │ ├── markdown-toggle.mdx │ ├── max-length.mdx │ ├── share-content.mdx │ ├── speech-to-text.mdx │ └── tree-view.mdx │ ├── auto-embed.mdx │ ├── auto-focus.mdx │ ├── autocomplete.mdx │ ├── code.mdx │ ├── collapsible.mdx │ ├── component-picker-menu.mdx │ ├── context-menu.mdx │ ├── drag-drop-paste.mdx │ ├── draggable-block.mdx │ ├── emoji.mdx │ ├── equation.mdx │ ├── excalidraw.mdx │ ├── floating-text-format.mdx │ ├── hashtag.mdx │ ├── horizontal-rule.mdx │ ├── image.mdx │ ├── index.mdx │ ├── inline-image.mdx │ ├── keywords.mdx │ ├── layout.mdx │ ├── link.mdx │ ├── markdown.mdx │ ├── mention.mdx │ ├── page-break.mdx │ ├── poll.mdx │ ├── rich-text-editor.mdx │ ├── tab-focus.mdx │ ├── table.mdx │ ├── toolbar.mdx │ ├── toolbar │ ├── block-format-toolbar.mdx │ ├── clear-formatting-toolbar.mdx │ ├── element-format-toolbar.mdx │ ├── font-color-toolbar.mdx │ ├── font-family-toolbar.mdx │ ├── font-format-toolbar.mdx │ ├── font-size-toolbar.mdx │ ├── history-toolbar.mdx │ ├── link-toolbar.mdx │ └── subsuper-toolbar.mdx │ └── typing-pref.mdx ├── eslint.config.mjs ├── hooks ├── use-colors.ts ├── use-config.ts ├── use-copy-to-clipboard.ts ├── use-is-mac.ts ├── use-media-query.tsx ├── use-meta-color.ts ├── use-mounted.ts └── use-mutation-observer.ts ├── lib ├── blocks.ts ├── colors.ts ├── config.ts ├── docs.ts ├── events.ts ├── fonts.ts ├── highlight-code.ts ├── registry.ts ├── rehype.ts ├── source.ts ├── themes.ts └── utils.ts ├── mdx-components.tsx ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── avatars │ └── shadcn.jpg ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── file.svg ├── globe.svg ├── next.svg ├── opengraph-image.png ├── placeholder.svg ├── r │ ├── actions-plugin-demo.json │ ├── actions-plugin.json │ ├── auto-embed-plugin-demo.json │ ├── auto-embed-plugin.json │ ├── auto-focus-plugin-demo.json │ ├── autocomplete-plugin-demo.json │ ├── autocomplete-plugin.json │ ├── block-format-toolbar-plugin-demo.json │ ├── block-format-toolbar-plugin.json │ ├── clear-editor-plugin-demo.json │ ├── clear-editor-plugin.json │ ├── clear-formatting-toolbar-plugin-demo.json │ ├── clear-formatting-toolbar-plugin.json │ ├── code-plugin-demo.json │ ├── code-plugin.json │ ├── collapsible-plugin-demo.json │ ├── collapsible-plugin.json │ ├── component-picker-menu-plugin-demo.json │ ├── component-picker-menu-plugin.json │ ├── context-menu-plugin-demo.json │ ├── context-menu-plugin.json │ ├── counter-character-plugin-demo.json │ ├── counter-character-plugin.json │ ├── drag-drop-paste-plugin-demo.json │ ├── drag-drop-paste-plugin.json │ ├── draggable-block-plugin-demo.json │ ├── draggable-block-plugin.json │ ├── edit-mode-toggle-plugin-demo.json │ ├── edit-mode-toggle-plugin.json │ ├── editor-x.json │ ├── editor.json │ ├── element-format-toolbar-plugin-demo.json │ ├── element-format-toolbar-plugin.json │ ├── emoji-plugin-demo.json │ ├── emoji-plugin.json │ ├── equation-plugin-demo.json │ ├── equation-plugin.json │ ├── excalidraw-plugin-demo.json │ ├── excalidraw-plugin.json │ ├── floating-text-format-plugin-demo.json │ ├── floating-text-format-plugin.json │ ├── font-color-toolbar-plugin-demo.json │ ├── font-color-toolbar-plugin.json │ ├── font-family-toolbar-plugin-demo.json │ ├── font-family-toolbar-plugin.json │ ├── font-format-toolbar-plugin-demo.json │ ├── font-format-toolbar-plugin.json │ ├── font-size-toolbar-plugin-demo.json │ ├── font-size-toolbar-plugin.json │ ├── hashtag-plugin-demo.json │ ├── history-toolbar-plugin-demo.json │ ├── history-toolbar-plugin.json │ ├── horizontal-rule-plugin-demo.json │ ├── horizontal-rule-plugin.json │ ├── image-plugin-demo.json │ ├── image-plugin.json │ ├── import-export-plugin-demo.json │ ├── import-export-plugin.json │ ├── index.json │ ├── inline-image-plugin-demo.json │ ├── inline-image-plugin.json │ ├── keywords-plugin-demo.json │ ├── keywords-plugin.json │ ├── layout-plugin-demo.json │ ├── layout-plugin.json │ ├── link-plugin-demo.json │ ├── link-plugin.json │ ├── link-toolbar-plugin-demo.json │ ├── link-toolbar-plugin.json │ ├── markdown-plugin-demo.json │ ├── markdown-toggle-plugin-demo.json │ ├── markdown-toggle-plugin.json │ ├── max-length-plugin-demo.json │ ├── max-length-plugin.json │ ├── mention-plugin-demo.json │ ├── mention-plugin.json │ ├── page-break-plugin-demo.json │ ├── page-break-plugin.json │ ├── poll-plugin-demo.json │ ├── poll-plugin.json │ ├── rich-text-editor-plugin-demo.json │ ├── rich-text-editor-plugin.json │ ├── share-content-plugin-demo.json │ ├── share-content-plugin.json │ ├── speech-to-text-plugin-demo.json │ ├── speech-to-text-plugin.json │ ├── subsuper-toolbar-plugin-demo.json │ ├── subsuper-toolbar-plugin.json │ ├── tab-focus-plugin-demo.json │ ├── tab-focus-plugin.json │ ├── table-plugin-demo.json │ ├── table-plugin.json │ ├── toolbar-plugin-demo.json │ ├── toolbar-plugin.json │ ├── tree-view-plugin-demo.json │ ├── tree-view-plugin.json │ ├── typing-pref-plugin-demo.json │ └── typing-pref-plugin.json ├── schema.json ├── schema │ ├── registry-item.json │ └── registry.json ├── site.webmanifest ├── twitter-image.png ├── vercel.svg └── window.svg ├── registry.json ├── registry ├── __index__.tsx ├── index.ts ├── new-york-v4 │ ├── blocks │ │ ├── editor-00 │ │ │ ├── editor.tsx │ │ │ ├── nodes.ts │ │ │ ├── page.tsx │ │ │ └── plugins.tsx │ │ └── editor-x │ │ │ ├── editor.tsx │ │ │ ├── nodes.ts │ │ │ ├── page.tsx │ │ │ └── plugins.tsx │ ├── editor │ │ ├── context │ │ │ ├── floating-link-context.tsx │ │ │ ├── shared-autocomplete-context.tsx │ │ │ └── toolbar-context.tsx │ │ ├── editor-hooks │ │ │ ├── use-debounce.ts │ │ │ ├── use-modal.tsx │ │ │ ├── use-report.ts │ │ │ └── use-update-toolbar.ts │ │ ├── editor-ui │ │ │ ├── code-button.tsx │ │ │ ├── colorpicker.tsx │ │ │ ├── content-editable.tsx │ │ │ ├── equation-component.tsx │ │ │ ├── equation-editor.tsx │ │ │ ├── excalidraw-component.tsx │ │ │ ├── excalidraw-image.tsx │ │ │ ├── excalidraw-modal.tsx │ │ │ ├── excalidraw.tsx │ │ │ ├── image-component.tsx │ │ │ ├── image-resizer.tsx │ │ │ ├── inline-image-component.tsx │ │ │ ├── katex-equation-alterer.tsx │ │ │ ├── katex-renderer.tsx │ │ │ ├── poll-component.tsx │ │ │ └── poll.css │ │ ├── index.tsx │ │ ├── nodes │ │ │ ├── autocomplete-node.tsx │ │ │ ├── collapsible-container-node.ts │ │ │ ├── collapsible-content-node.ts │ │ │ ├── collapsible-title-node.ts │ │ │ ├── embeds │ │ │ │ ├── figma-node.tsx │ │ │ │ ├── tweet-node.tsx │ │ │ │ └── youtube-node.tsx │ │ │ ├── emoji-node.tsx │ │ │ ├── equation-node.tsx │ │ │ ├── excalidraw-node.tsx │ │ │ ├── image-node.tsx │ │ │ ├── inline-image-node.tsx │ │ │ ├── keyword-node.tsx │ │ │ ├── layout-container-node.tsx │ │ │ ├── layout-item-node.tsx │ │ │ ├── mention-node.ts │ │ │ ├── nodes.ts │ │ │ ├── page-break-node.css │ │ │ ├── page-break-node.tsx │ │ │ └── poll-node.tsx │ │ ├── plugins │ │ │ ├── actions │ │ │ │ ├── actions-plugin.tsx │ │ │ │ ├── character-limit-plugin.tsx │ │ │ │ ├── clear-editor-plugin.tsx │ │ │ │ ├── counter-character-plugin.tsx │ │ │ │ ├── edit-mode-toggle-plugin.tsx │ │ │ │ ├── import-export-plugin.tsx │ │ │ │ ├── markdown-toggle-plugin.tsx │ │ │ │ ├── max-length-plugin.tsx │ │ │ │ ├── share-content-plugin.tsx │ │ │ │ ├── speech-to-text-plugin.tsx │ │ │ │ └── tree-view-plugin.tsx │ │ │ ├── auto-link-plugin.tsx │ │ │ ├── autocomplete-plugin.tsx │ │ │ ├── code-action-menu-plugin.tsx │ │ │ ├── code-highlight-plugin.tsx │ │ │ ├── collapsible-plugin.tsx │ │ │ ├── component-picker-menu-plugin.tsx │ │ │ ├── context-menu-plugin.tsx │ │ │ ├── default │ │ │ │ ├── lexical-context-menu-plugin.tsx │ │ │ │ └── lexical-typeahead-menu-plugin.ts │ │ │ ├── drag-drop-paste-plugin.tsx │ │ │ ├── draggable-block-plugin.tsx │ │ │ ├── embeds │ │ │ │ ├── auto-embed-plugin.tsx │ │ │ │ ├── figma-plugin.tsx │ │ │ │ ├── twitter-plugin.tsx │ │ │ │ └── youtube-plugin.tsx │ │ │ ├── emoji-picker-plugin.tsx │ │ │ ├── emojis-plugin.tsx │ │ │ ├── equations-plugin.tsx │ │ │ ├── excalidraw-plugin.tsx │ │ │ ├── floating-link-editor-plugin.tsx │ │ │ ├── floating-text-format-plugin.tsx │ │ │ ├── images-plugin.tsx │ │ │ ├── inline-image-plugin.tsx │ │ │ ├── keywords-plugin.tsx │ │ │ ├── layout-plugin.tsx │ │ │ ├── link-plugin.tsx │ │ │ ├── list-max-indent-level-plugin.tsx │ │ │ ├── mentions-plugin.tsx │ │ │ ├── page-break-plugin.tsx │ │ │ ├── picker │ │ │ │ ├── alignment-picker-plugin.tsx │ │ │ │ ├── bulleted-list-picker-plugin.tsx │ │ │ │ ├── check-list-picker-plugin.tsx │ │ │ │ ├── code-picker-plugin.tsx │ │ │ │ ├── collapsible-picker-plugin.tsx │ │ │ │ ├── columns-layout-picker-plugin.tsx │ │ │ │ ├── component-picker-option.tsx │ │ │ │ ├── divider-picker-plugin.tsx │ │ │ │ ├── embeds-picker-plugin.tsx │ │ │ │ ├── equation-picker-plugin.tsx │ │ │ │ ├── excalidraw-picker-plugin.tsx │ │ │ │ ├── heading-picker-plugin.tsx │ │ │ │ ├── image-picker-plugin.tsx │ │ │ │ ├── numbered-list-picker-plugin.tsx │ │ │ │ ├── page-break-picker-plugin.tsx │ │ │ │ ├── paragraph-picker-plugin.tsx │ │ │ │ ├── poll-picker-plugin.tsx │ │ │ │ ├── quote-picker-plugin.tsx │ │ │ │ └── table-picker-plugin.tsx │ │ │ ├── plugins.tsx │ │ │ ├── poll-plugin.tsx │ │ │ ├── tab-focus-plugin.tsx │ │ │ ├── table-action-menu-plugin.tsx │ │ │ ├── table-cell-resizer-plugin.tsx │ │ │ ├── table-hover-actions-plugin.tsx │ │ │ ├── table-plugin.tsx │ │ │ ├── toolbar │ │ │ │ ├── block-format-toolbar-plugin.tsx │ │ │ │ ├── block-format │ │ │ │ │ ├── block-format-data.tsx │ │ │ │ │ ├── format-bulleted-list.tsx │ │ │ │ │ ├── format-check-list.tsx │ │ │ │ │ ├── format-code-block.tsx │ │ │ │ │ ├── format-heading.tsx │ │ │ │ │ ├── format-numbered-list.tsx │ │ │ │ │ ├── format-paragraph.tsx │ │ │ │ │ └── format-quote.tsx │ │ │ │ ├── block-insert-plugin.tsx │ │ │ │ ├── block-insert │ │ │ │ │ ├── insert-collapsible-container.tsx │ │ │ │ │ ├── insert-columns-layout.tsx │ │ │ │ │ ├── insert-embeds.tsx │ │ │ │ │ ├── insert-equation.tsx │ │ │ │ │ ├── insert-excalidraw.tsx │ │ │ │ │ ├── insert-horizontal-rule.tsx │ │ │ │ │ ├── insert-image.tsx │ │ │ │ │ ├── insert-inline-image.tsx │ │ │ │ │ ├── insert-page-break.tsx │ │ │ │ │ ├── insert-poll.tsx │ │ │ │ │ └── insert-table.tsx │ │ │ │ ├── clear-formatting-toolbar-plugin.tsx │ │ │ │ ├── code-language-toolbar-plugin.tsx │ │ │ │ ├── element-format-toolbar-plugin.tsx │ │ │ │ ├── font-background-toolbar-plugin.tsx │ │ │ │ ├── font-color-toolbar-plugin.tsx │ │ │ │ ├── font-family-toolbar-plugin.tsx │ │ │ │ ├── font-format-toolbar-plugin.tsx │ │ │ │ ├── font-size-toolbar-plugin.tsx │ │ │ │ ├── history-toolbar-plugin.tsx │ │ │ │ ├── link-toolbar-plugin.tsx │ │ │ │ ├── subsuper-toolbar-plugin.tsx │ │ │ │ └── toolbar-plugin.tsx │ │ │ └── typing-pref-plugin.tsx │ │ ├── shared │ │ │ ├── can-use-dom.ts │ │ │ ├── caret-from-point.ts │ │ │ ├── environment.ts │ │ │ ├── invariant.ts │ │ │ ├── normalize-class-names.ts │ │ │ ├── react-patches.ts │ │ │ ├── react-test-utils.ts │ │ │ ├── simple-diff-with-cursor.ts │ │ │ ├── use-layout-effect.ts │ │ │ └── warn-only-once.ts │ │ ├── themes │ │ │ ├── editor-theme.css │ │ │ └── editor-theme.ts │ │ ├── transformers │ │ │ ├── markdown-emoji-transformer.ts │ │ │ ├── markdown-equation-transformer.ts │ │ │ ├── markdown-hr-transformer.ts │ │ │ ├── markdown-image-transformer.ts │ │ │ ├── markdown-table-transformer.ts │ │ │ └── markdown-tweet-transformer.ts │ │ └── utils │ │ │ ├── collapsible.ts │ │ │ ├── doc-serialization.ts │ │ │ ├── emoji-list.ts │ │ │ ├── get-dom-range-rect.ts │ │ │ ├── get-selected-node.ts │ │ │ ├── guard.ts │ │ │ ├── is-mobile-width.ts │ │ │ ├── set-floating-elem-position-for-link-editor.ts │ │ │ ├── set-floating-elem-position.ts │ │ │ ├── swipe.ts │ │ │ └── url.ts │ ├── examples │ │ ├── actions-plugin-demo.tsx │ │ ├── auto-embed-plugin-demo.tsx │ │ ├── auto-focus-plugin-demo.tsx │ │ ├── autocomplete-plugin-demo.tsx │ │ ├── block-format-toolbar-demo.tsx │ │ ├── block-format-toolbar-plugin-demo.tsx │ │ ├── clear-editor-plugin-demo.tsx │ │ ├── clear-formatting-toolbar-plugin-demo.tsx │ │ ├── code-plugin-demo.tsx │ │ ├── collapsible-plugin-demo.tsx │ │ ├── component-picker-menu-plugin-demo.tsx │ │ ├── context-menu-plugin-demo.tsx │ │ ├── counter-character-plugin-demo.tsx │ │ ├── drag-drop-paste-plugin-demo.tsx │ │ ├── draggable-block-plugin-demo.tsx │ │ ├── edit-mode-toggle-plugin-demo.tsx │ │ ├── element-format-toolbar-plugin-demo.tsx │ │ ├── emoji-plugin-demo.tsx │ │ ├── equation-plugin-demo.tsx │ │ ├── excalidraw-plugin-demo.tsx │ │ ├── floating-text-format-plugin-demo.tsx │ │ ├── font-color-toolbar-plugin-demo.tsx │ │ ├── font-family-toolbar-plugin-demo.tsx │ │ ├── font-format-toolbar-plugin-demo.tsx │ │ ├── font-size-toolbar-plugin-demo.tsx │ │ ├── hashtag-plugin-demo.tsx │ │ ├── history-toolbar-demo.tsx │ │ ├── history-toolbar-plugin-demo.tsx │ │ ├── horizontal-rule-plugin-demo.tsx │ │ ├── image-plugin-demo.tsx │ │ ├── import-export-plugin-demo.tsx │ │ ├── inline-image-plugin-demo.tsx │ │ ├── keywords-plugin-demo.tsx │ │ ├── layout-plugin-demo.tsx │ │ ├── link-plugin-demo.tsx │ │ ├── link-toolbar-plugin-demo.tsx │ │ ├── markdown-plugin-demo.tsx │ │ ├── markdown-toggle-plugin-demo.tsx │ │ ├── max-length-plugin-demo.tsx │ │ ├── mention-plugin-demo.tsx │ │ ├── page-break-plugin-demo.tsx │ │ ├── poll-plugin-demo.tsx │ │ ├── rich-text-editor-demo.tsx │ │ ├── rich-text-editor-plugin-demo.tsx │ │ ├── share-content-plugin-demo.tsx │ │ ├── speech-to-text-plugin-demo.tsx │ │ ├── subsuper-toolbar-plugin-demo.tsx │ │ ├── tab-focus-plugin-demo.tsx │ │ ├── table-plugin-demo.tsx │ │ ├── toolbar-demo.tsx │ │ ├── toolbar-plugin-demo.tsx │ │ ├── tree-view-plugin-demo.tsx │ │ └── typing-pref-plugin-demo.tsx │ ├── hooks │ │ └── use-mobile.ts │ ├── lib │ │ └── utils.ts │ └── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ └── tooltip.tsx ├── registry-base-colors.ts ├── registry-blocks.ts ├── registry-categories.ts ├── registry-charts.ts ├── registry-colors.ts ├── registry-examples.ts ├── registry-hooks.ts ├── registry-icons.ts ├── registry-internal.ts ├── registry-lib.ts ├── registry-themes.ts └── registry-ui.ts ├── scripts ├── build-registry.mts └── capture-registry.mts ├── source.config.ts ├── styles ├── globals.css └── themes.css ├── tsconfig.json └── tsconfig.scripts.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | 44 | # generated content 45 | .contentlayer 46 | .content-collections 47 | .source 48 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .next 4 | build 5 | .contentlayer 6 | registry/__index__.tsx 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a wip registry for the `shadcn` canary version. It has React 19 and Tailwind v4 components. 2 | -------------------------------------------------------------------------------- /app/(app)/(root)/section.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { SerializedEditorState } from "lexical" 5 | 6 | import { Editor } from "@/components/blocks/editor/editor" 7 | 8 | export const initialValue = { 9 | root: { 10 | children: [ 11 | { 12 | children: [ 13 | { 14 | detail: 0, 15 | format: 0, 16 | mode: "normal", 17 | style: "", 18 | text: "Hello World 🚀", 19 | type: "text", 20 | version: 1, 21 | }, 22 | ], 23 | direction: "ltr", 24 | format: "", 25 | indent: 0, 26 | type: "paragraph", 27 | version: 1, 28 | }, 29 | ], 30 | direction: "ltr", 31 | format: "", 32 | indent: 0, 33 | type: "root", 34 | version: 1, 35 | }, 36 | } as unknown as SerializedEditorState 37 | 38 | export function EditorSection() { 39 | const [editorState, setEditorState] = 40 | useState(initialValue) 41 | 42 | return ( 43 | setEditorState(value)} 46 | /> 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /app/(app)/blocks/[...categories]/page.tsx: -------------------------------------------------------------------------------- 1 | import { getAllBlockIds } from "@/lib/blocks" 2 | import { BlockDisplay } from "@/components/block-display" 3 | import { registryCategories } from "@/registry/registry-categories" 4 | 5 | export const revalidate = false 6 | export const dynamic = "force-static" 7 | export const dynamicParams = false 8 | 9 | export async function generateStaticParams() { 10 | return registryCategories.map((category) => ({ 11 | categories: [category.slug], 12 | })) 13 | } 14 | 15 | export default async function BlocksPage({ 16 | params, 17 | }: { 18 | params: Promise<{ categories?: string[] }> 19 | }) { 20 | const { categories = [] } = await params 21 | const blocks = await getAllBlockIds(["registry:block"], categories) 22 | 23 | return ( 24 |
25 | {blocks.map((name) => ( 26 | 27 | ))} 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /app/(app)/blocks/page.tsx: -------------------------------------------------------------------------------- 1 | import { BlockDisplay } from "@/components/block-display" 2 | 3 | export const dynamic = "force-static" 4 | export const revalidate = false 5 | 6 | const FEATURED_BLOCKS = ["editor-x"] 7 | 8 | export default async function BlocksPage() { 9 | return ( 10 |
11 | {FEATURED_BLOCKS.map((name) => ( 12 | 13 | ))} 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /app/(app)/docs/layout.tsx: -------------------------------------------------------------------------------- 1 | import { docsConfig } from "@/lib/docs" 2 | import { DocsSidebar } from "@/components/docs-sidebar" 3 | import { SidebarProvider } from "@/registry/new-york-v4/ui/sidebar" 4 | 5 | export default function DocsLayout({ 6 | children, 7 | }: { 8 | children: React.ReactNode 9 | }) { 10 | return ( 11 |
12 | 13 | 14 |
{children}
15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /app/(app)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { SiteFooter } from "@/components/site-footer" 2 | import { SiteHeader } from "@/components/site-header" 3 | 4 | export default function AppLayout({ children }: { children: React.ReactNode }) { 5 | return ( 6 |
7 | 8 |
{children}
9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/registry/new-york-v4/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /components/analytics.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Analytics as VercelAnalytics } from "@vercel/analytics/react" 4 | 5 | export function Analytics() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /components/announcement.tsx: -------------------------------------------------------------------------------- 1 | export function Announcement() { 2 | return null 3 | } 4 | -------------------------------------------------------------------------------- /components/block-display.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { registryItemFileSchema } from "shadcn/registry" 3 | import { z } from "zod" 4 | 5 | import { highlightCode } from "@/lib/highlight-code" 6 | import { 7 | createFileTreeForRegistryItemFiles, 8 | getRegistryItem, 9 | } from "@/lib/registry" 10 | import { BlockViewer } from "@/components/block-viewer" 11 | 12 | export async function BlockDisplay({ name }: { name: string }) { 13 | const item = await getCachedRegistryItem(name) 14 | 15 | if (!item?.files) { 16 | return null 17 | } 18 | 19 | const [tree, highlightedFiles] = await Promise.all([ 20 | getCachedFileTree(item.files), 21 | getCachedHighlightedFiles(item.files), 22 | ]) 23 | 24 | return ( 25 | 26 | ) 27 | } 28 | 29 | const getCachedRegistryItem = React.cache(async (name: string) => { 30 | return await getRegistryItem(name) 31 | }) 32 | 33 | const getCachedFileTree = React.cache( 34 | async (files: Array<{ path: string; target?: string }>) => { 35 | if (!files) { 36 | return null 37 | } 38 | 39 | return await createFileTreeForRegistryItemFiles(files) 40 | } 41 | ) 42 | 43 | const getCachedHighlightedFiles = React.cache( 44 | async (files: z.infer[]) => { 45 | return await Promise.all( 46 | files.map(async (file) => ({ 47 | ...file, 48 | highlightedContent: await highlightCode(file.content ?? ""), 49 | })) 50 | ) 51 | } 52 | ) 53 | -------------------------------------------------------------------------------- /components/block-image.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export function BlockImage({ 6 | name, 7 | width = 1440, 8 | height = 900, 9 | className, 10 | }: Omit, "src" | "alt"> & { name: string }) { 11 | return ( 12 |
18 | {name} 26 | {name} 34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /components/callout.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | import { 3 | Alert, 4 | AlertDescription, 5 | AlertTitle, 6 | } from "@/registry/new-york-v4/ui/alert" 7 | 8 | export function Callout({ 9 | title, 10 | children, 11 | icon, 12 | className, 13 | ...props 14 | }: React.ComponentProps & { icon?: React.ReactNode }) { 15 | return ( 16 | 23 | {icon} 24 | {title && {title}} 25 | 26 | {children} 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /components/cards/calendar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { addDays } from "date-fns" 4 | 5 | import { Calendar } from "@/registry/new-york-v4/ui/calendar" 6 | import { Card, CardContent } from "@/registry/new-york-v4/ui/card" 7 | 8 | const start = new Date(2025, 5, 5) 9 | 10 | export function CardsCalendar() { 11 | return ( 12 | 13 | 14 | 23 | 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /components/code-tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | 5 | import { useConfig } from "@/hooks/use-config" 6 | import { Tabs } from "@/registry/new-york-v4/ui/tabs" 7 | 8 | export function CodeTabs({ children }: React.ComponentProps) { 9 | const [config, setConfig] = useConfig() 10 | 11 | const installationType = React.useMemo(() => { 12 | return config.installationType || "cli" 13 | }, [config]) 14 | 15 | return ( 16 | 19 | setConfig({ ...config, installationType: value as "cli" | "manual" }) 20 | } 21 | className="relative mt-6 w-full" 22 | > 23 | {children} 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /components/color-palette.tsx: -------------------------------------------------------------------------------- 1 | import { type ColorPalette } from "@/lib/colors" 2 | import { Color } from "@/components/color" 3 | import { ColorFormatSelector } from "@/components/color-format-selector" 4 | 5 | export function ColorPalette({ colorPalette }: { colorPalette: ColorPalette }) { 6 | return ( 7 |
8 |
9 |
10 |

{colorPalette.name}

11 |
12 | 16 |
17 |
18 | {colorPalette.colors.map((color) => ( 19 | 20 | ))} 21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /components/colors-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link" 4 | import { usePathname } from "next/navigation" 5 | 6 | import { getColors } from "@/lib/colors" 7 | import { cn } from "@/lib/utils" 8 | import { ScrollArea, ScrollBar } from "@/registry/new-york-v4/ui/scroll-area" 9 | 10 | export function ColorsNav({ 11 | className, 12 | ...props 13 | }: React.ComponentProps<"div">) { 14 | const pathname = usePathname() 15 | const colors = getColors() 16 | 17 | return ( 18 |
19 | 20 |
21 | {colors.map((colorPalette, index) => ( 22 | 33 | {colorPalette.name} 34 | 35 | ))} 36 |
37 | 38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /components/components-list.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | 3 | import { source } from "@/lib/source" 4 | 5 | export function ComponentsList() { 6 | const components = source.pageTree.children.find( 7 | (page) => page.$id === "components" 8 | ) 9 | 10 | if (components?.type !== "folder") { 11 | return 12 | } 13 | 14 | const list = components.children.filter( 15 | (component) => component.type === "page" 16 | ) 17 | 18 | return ( 19 |
20 | {list.map((component) => ( 21 | 26 | {component.name} 27 | 28 | ))} 29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /components/docs-copy-page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { IconCheck, IconCopy } from "@tabler/icons-react" 4 | 5 | import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard" 6 | import { Button } from "@/registry/new-york-v4/ui/button" 7 | import { 8 | Tooltip, 9 | TooltipContent, 10 | TooltipTrigger, 11 | } from "@/registry/new-york-v4/ui/tooltip" 12 | 13 | export function DocsCopyPage({ page }: { page: string }) { 14 | const { copyToClipboard, isCopied } = useCopyToClipboard() 15 | 16 | return ( 17 | 18 | 19 | 27 | 28 | 29 |

Copy as Markdown

30 |
31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /components/github-link.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import Link from "next/link" 3 | 4 | import { siteConfig } from "@/lib/config" 5 | import { Icons } from "@/components/icons" 6 | import { Button } from "@/registry/new-york-v4/ui/button" 7 | import { Skeleton } from "@/registry/new-york-v4/ui/skeleton" 8 | 9 | export function GitHubLink() { 10 | return ( 11 | 19 | ) 20 | } 21 | 22 | export async function StarsCount() { 23 | const data = await fetch( 24 | "https://api.github.com/repos/htmujahid/shadcn-editor", 25 | { 26 | next: { revalidate: 86400 }, // Cache for 1 day (86400 seconds) 27 | } 28 | ) 29 | const json = await data.json() 30 | 31 | return ( 32 | 33 | {json.stargazers_count >= 1000 34 | ? `${(json.stargazers_count / 1000).toFixed(1)}k` 35 | : json.stargazers_count.toLocaleString()} 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /components/lo-fi/accordion.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronDownIcon, ChevronUpIcon } from "lucide-react" 2 | 3 | import { Atom } from "@/components/lo-fi/atom" 4 | 5 | export function AccordionLoFi() { 6 | return ( 7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /components/lo-fi/alert.tsx: -------------------------------------------------------------------------------- 1 | import { CircleAlertIcon } from "lucide-react" 2 | 3 | import { Atom } from "@/components/lo-fi/atom" 4 | 5 | export function AlertLoFi() { 6 | return ( 7 | 11 | 12 |
13 | 14 | 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /components/lo-fi/atom.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const atomVariants = cva( 7 | "inline-flex rounded-lg border-neutral-300 dark:border-neutral-600", 8 | { 9 | variants: { 10 | shade: { 11 | "50": "bg-neutral-50 dark:bg-neutral-900", 12 | "100": "bg-neutral-100 dark:bg-neutral-800", 13 | "200": "bg-neutral-200 dark:bg-neutral-700", 14 | "300": "bg-neutral-300 dark:bg-neutral-600", 15 | "400": "bg-neutral-400 dark:bg-neutral-500", 16 | "500": "bg-neutral-500 dark:bg-neutral-400", 17 | "600": "bg-neutral-600 dark:bg-neutral-300", 18 | "700": "bg-neutral-700 dark:bg-neutral-200", 19 | "800": "bg-neutral-800 dark:bg-neutral-100", 20 | "900": "bg-neutral-900 dark:bg-neutral-50", 21 | }, 22 | }, 23 | defaultVariants: { 24 | shade: "50", 25 | }, 26 | } 27 | ) 28 | 29 | function Atom({ 30 | className, 31 | shade, 32 | ...props 33 | }: React.ComponentProps<"div"> & VariantProps) { 34 | return ( 35 |
40 | ) 41 | } 42 | 43 | export { Atom } 44 | -------------------------------------------------------------------------------- /components/lo-fi/component.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | 3 | import { Atom } from "@/components/lo-fi/atom" 4 | 5 | function Component({ href, ...props }: React.ComponentProps) { 6 | return 7 | } 8 | 9 | function ComponentContent({ ...props }: React.ComponentProps) { 10 | return ( 11 | 16 | ) 17 | } 18 | 19 | function ComponentName({ ...props }: React.ComponentProps<"div">) { 20 | return ( 21 |
25 | ) 26 | } 27 | 28 | export { Component, ComponentContent, ComponentName } 29 | -------------------------------------------------------------------------------- /components/lo-fi/index.tsx: -------------------------------------------------------------------------------- 1 | import { AccordionLoFi } from "@/components/lo-fi/accordion" 2 | import { AlertLoFi } from "@/components/lo-fi/alert" 3 | import { 4 | Component, 5 | ComponentContent, 6 | ComponentName, 7 | } from "@/components/lo-fi/component" 8 | 9 | export function LoFi() { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | Accordion 17 | 18 | 19 | 20 | 21 | 22 | Alert 23 | 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /components/main-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link" 4 | import { usePathname } from "next/navigation" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { Button } from "@/registry/new-york-v4/ui/button" 8 | 9 | export function MainNav({ 10 | items, 11 | className, 12 | ...props 13 | }: React.ComponentProps<"nav"> & { 14 | items: { href: string; label: string }[] 15 | }) { 16 | const pathname = usePathname() 17 | 18 | return ( 19 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /components/mode-switcher.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { useTheme } from "next-themes" 5 | 6 | import { useMetaColor } from "@/hooks/use-meta-color" 7 | import { Button } from "@/registry/new-york-v4/ui/button" 8 | 9 | export function ModeSwitcher() { 10 | const { setTheme, resolvedTheme } = useTheme() 11 | const { setMetaColor, metaColor } = useMetaColor() 12 | 13 | React.useEffect(() => { 14 | setMetaColor(metaColor) 15 | }, [metaColor, setMetaColor]) 16 | 17 | const toggleTheme = React.useCallback(() => { 18 | setTheme(resolvedTheme === "dark" ? "light" : "dark") 19 | }, [resolvedTheme, setTheme]) 20 | 21 | return ( 22 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /components/nav-header.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link" 4 | import { usePathname } from "next/navigation" 5 | 6 | import { 7 | NavigationMenu, 8 | NavigationMenuItem, 9 | NavigationMenuLink, 10 | NavigationMenuList, 11 | } from "@/registry/new-york-v4/ui/navigation-menu" 12 | 13 | export function NavHeader() { 14 | const pathname = usePathname() 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | Home 22 | 23 | 24 | 25 | 26 | Charts 27 | 28 | 29 | 30 | 31 | Forms 32 | 33 | 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /components/open-in-v0-button.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | import { Icons } from "@/components/icons" 3 | import { Button } from "@/registry/new-york-v4/ui/button" 4 | 5 | // v0 uses the default style. 6 | const V0_STYLE = "default" 7 | 8 | export function OpenInV0Button({ 9 | name, 10 | className, 11 | ...props 12 | }: React.ComponentProps & { 13 | name: string 14 | }) { 15 | return ( 16 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /components/open-in-v0-cta.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | import { Button } from "@/registry/new-york-v4/ui/button" 3 | 4 | export function OpenInV0Cta({ className }: React.ComponentProps<"div">) { 5 | return null; 6 | return ( 7 |
13 |
14 | Deploy your shadcn/ui app on Vercel 15 |
16 |
17 | Trusted by OpenAI, Sonos, Chick-fil-A, and more. 18 |
19 |
20 | Vercel provides tools and infrastructure to deploy apps and features at 21 | scale. 22 |
23 | 26 | 32 | Deploy to Vercel 33 | 34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /components/page-nav.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | export function PageNav({ 4 | children, 5 | className, 6 | ...props 7 | }: React.ComponentProps<"div">) { 8 | return ( 9 |
10 |
11 | {children} 12 |
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /components/site-footer.tsx: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "@/lib/config" 2 | 3 | export function SiteFooter() { 4 | return ( 5 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /components/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | const SHOW = false 2 | 3 | export function TailwindIndicator() { 4 | if (process.env.NODE_ENV === "production" || !SHOW) { 5 | return null 6 | } 7 | 8 | return ( 9 |
13 |
xs
14 |
sm
15 |
md
16 |
lg
17 |
xl
18 |
2xl
19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /content/docs/(root)/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Get Started", 3 | "pages": [ 4 | "index", 5 | "[Installation](/docs/installation)", 6 | "[Plugins](/docs/plugins)" 7 | ] 8 | } -------------------------------------------------------------------------------- /content/docs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "pages": [ 4 | "(root)", 5 | "plugins" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /content/docs/plugins/actions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Actions 3 | description: A plugin that provides core editor actions and functionality for the rich text editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/actions-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/clear-editor.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clear Editor 3 | description: A plugin that provides a dialog to safely clear all content from the editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/clear-editor-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/counter-character.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Counter Character 3 | description: A plugin that displays and tracks the current character count in the editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/counter-character-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/text 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/edit-mode-toggle.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Edit Mode Toggle 3 | description: A plugin that enables switching between edit and preview modes of the editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/edit-mode-toggle-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/import-export.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Import Export 3 | description: A plugin that enables importing and exporting editor content with file handling capabilities. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/import-export-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/file 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/markdown-toggle.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown Toggle 3 | description: A plugin that enables switching between rich text and markdown editing modes. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/markdown-toggle-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/markdown @lexical/code 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/max-length.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Max Length 3 | description: A plugin that enforces a maximum character limit on the editor content. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/max-length-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/share-content.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Share Content 3 | description: A plugin that enables content sharing functionality with file export and toast notifications. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/share-content-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/file sonner 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/speech-to-text.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Speech to Text 3 | description: A plugin that enables voice input functionality with browser's speech recognition API. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/speech-to-text-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/actions/tree-view.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tree View 3 | description: A plugin that provides a visual tree view of the editor's document structure in a dialog. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/tree-view-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/auto-embed.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Auto Embed 3 | description: A plugin that provides automatic embedding of content from various platforms including Twitter, YouTube, and Figma. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/auto-embed-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils @lexical/markdown 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/auto-focus.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Auto Focus 3 | description: A plugin that automatically focuses the editor when it is mounted. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/auto-focus-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/autocomplete.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Autocomplete 3 | description: A plugin that provides text autocompletion with swipe support and shared context. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/autocomplete-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/code.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Code 3 | description: A plugin that provides code block formatting, syntax highlighting, and language selection. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/code-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install lodash-es @lexical/utils @lexical/code @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/collapsible.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collapsible 3 | description: A plugin that provides collapsible sections with title and content nodes. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/collapsible-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/component-picker-menu.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Component Picker Menu 3 | description: A plugin that provides a modal-based component picker with typeahead menu support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/component-picker-menu-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/context-menu.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Context Menu 3 | description: A plugin that provides a popover-based context menu with link support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/context-menu-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/link 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/drag-drop-paste.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Drag Drop Paste 3 | description: A plugin that provides drag and drop functionality with image paste support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/drag-drop-paste-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/draggable-block.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Draggable Block 3 | description: A plugin that enables drag and drop functionality for editor blocks. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/draggable-block-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/emoji.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Emoji 3 | description: A plugin that provides emoji picker, insertion, and markdown support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/emoji-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/equation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Equation 3 | description: A plugin that provides LaTeX equation editing and rendering with KaTeX support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/equation-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install katex react-error-boundary @lexical/utils @lexical/markdown 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/excalidraw.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Excalidraw 3 | description: A plugin that provides a drawing canvas with Excalidraw integration and modal interface. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/excalidraw-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @excalidraw/excalidraw @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/floating-text-format.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Floating Text Format 3 | description: A plugin that provides a floating toolbar for text formatting with code, link, and selection support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/floating-text-format-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/code @lexical/link @lexical/utils @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/hashtag.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hashtag 3 | description: A plugin that enables hashtag functionality with automatic detection and styling. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/hashtag-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/hashtag 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/horizontal-rule.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Horizontal Rule 3 | description: A plugin that provides horizontal rule insertion and styling functionality. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/horizontal-rule-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/image.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Image 3 | description: A plugin that provides image insertion, resizing, and markdown support with a dialog interface. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/image-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils @lexical/markdown 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/inline-image.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Inline Image 3 | description: A plugin that provides inline image insertion with link support and a dialog interface. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/inline-image-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/keywords.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Keywords 3 | description: A plugin that provides keyword highlighting and styling functionality. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/keywords-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/layout.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Layout 3 | description: A plugin that provides column-based layout containers with customizable structure. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/layout-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/link.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Link 3 | description: A plugin that provides URL validation and link handling functionality for the editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/link-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/markdown.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown 3 | description: A plugin that provides markdown parsing and serialization support for the editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/markdown-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/markdown 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/mention.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mention 3 | description: A plugin that provides user mention functionality with typeahead menu support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/mention-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/page-break.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page Break 3 | description: A plugin that provides page break insertion and styling functionality. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/page-break-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/poll.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Poll 3 | description: A plugin that provides poll creation and voting functionality with a dialog interface. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/poll-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/rich-text-editor.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Rich Text Editor 3 | description: A content editable component for the Lexical editor that provides rich text editing capabilities. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/rich-text-editor-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/rich-text 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/tab-focus.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tab Focus 3 | description: A plugin that manages tab focus behavior within the editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/tab-focus-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/table.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Table 3 | description: A plugin that provides table creation, editing, resizing, and markdown table support. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/table-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install lodash-es react-colorful @lexical/table @lexical/utils @lexical/markdown 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Toolbar 3 | description: A toolbar plugin that provides formatting controls and context management for the rich text editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/block-format-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Block Format 3 | description: A toolbar plugin that provides block-level formatting options including lists, paragraphs, and quotes. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/block-format-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/list @lexical/utils @lexical/selection @lexical/code 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/clear-formatting-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clear Formatting Toolbar 3 | description: A toolbar plugin that provides a button to remove all text formatting from the selected content. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/clear-formatting-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils @lexical/table 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/element-format-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Element Format Toolbar 3 | description: A toolbar plugin that provides element-level formatting controls for text alignment and indentation. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/element-format-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/link @lexical/utils @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/font-color-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Font Color Toolbar 3 | description: A toolbar plugin that provides text color and background color selection with a color picker. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/font-color-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install react-colorful @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/font-family-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Font Family Toolbar 3 | description: A font family toolbar plugin. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/font-family-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/font-format-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Font Format Toolbar 3 | description: A toolbar plugin that provides font family selection and text formatting options. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/font-format-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/table 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/font-size-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Font Size Toolbar 3 | description: A toolbar plugin that provides font size selection and adjustment controls. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/font-size-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/history-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: History Toolbar 3 | description: A toolbar plugin that provides undo/redo functionality for the rich text editor. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/history-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/utils 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/link-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Link Toolbar 3 | description: A toolbar plugin that provides link creation, editing, and removal functionality with URL validation. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/link-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/link @lexical/utils @lexical/selection 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/toolbar/subsuper-toolbar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Subsuper Toolbar 3 | description: A toolbar plugin that provides subscript and superscript text formatting options. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/subsuper-toolbar-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Install the following dependencies: 34 | 35 | ```bash 36 | npm install @lexical/table 37 | ``` 38 | 39 | Copy and paste the following code into your project. 40 | 41 | 42 | 43 | Update the import paths to match your project setup. 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /content/docs/plugins/typing-pref.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Typing Pref 3 | description: A plugin that provides typing preferences and reporting functionality. 4 | --- 5 | 6 | 11 | 12 | ## Installation 13 | 14 | 15 | 16 | 17 | CLI 18 | Manual 19 | 20 | 21 | 22 | 23 | ```bash 24 | npx shadcn@latest add https://shadcn-editor.vercel.app/r/typing-pref-plugin.json 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Copy and paste the following code into your project. 34 | 35 | 36 | 37 | Update the import paths to match your project setup. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path" 2 | import { fileURLToPath } from "url" 3 | import { FlatCompat } from "@eslint/eslintrc" 4 | 5 | const __filename = fileURLToPath(import.meta.url) 6 | const __dirname = dirname(__filename) 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }) 11 | 12 | const eslintConfig = [ 13 | ...compat.config({ 14 | extends: ["next/core-web-vitals", "next/typescript"], 15 | rules: { 16 | "@next/next/no-duplicate-head": "off", 17 | }, 18 | }), 19 | ] 20 | 21 | export default eslintConfig 22 | -------------------------------------------------------------------------------- /hooks/use-colors.ts: -------------------------------------------------------------------------------- 1 | import { useAtom } from "jotai" 2 | import { atomWithStorage } from "jotai/utils" 3 | 4 | import { ColorFormat } from "@/lib/colors" 5 | import { useMounted } from "@/hooks/use-mounted" 6 | 7 | type Config = { 8 | format: ColorFormat 9 | lastCopied: string 10 | } 11 | 12 | const colorsAtom = atomWithStorage("colors", { 13 | format: "hsl", 14 | lastCopied: "", 15 | }) 16 | 17 | export function useColors() { 18 | const [colors, setColors] = useAtom(colorsAtom) 19 | const mounted = useMounted() 20 | 21 | return { 22 | isLoading: !mounted, 23 | format: colors.format, 24 | lastCopied: colors.lastCopied, 25 | setFormat: (format: ColorFormat) => setColors({ ...colors, format }), 26 | setLastCopied: (lastCopied: string) => setColors({ ...colors, lastCopied }), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hooks/use-config.ts: -------------------------------------------------------------------------------- 1 | import { useAtom } from "jotai" 2 | import { atomWithStorage } from "jotai/utils" 3 | 4 | type Config = { 5 | style: "new-york-v4" 6 | packageManager: "npm" | "yarn" | "pnpm" | "bun" 7 | installationType: "cli" | "manual" 8 | } 9 | 10 | const configAtom = atomWithStorage("config", { 11 | style: "new-york-v4", 12 | packageManager: "pnpm", 13 | installationType: "cli", 14 | }) 15 | 16 | export function useConfig() { 17 | return useAtom(configAtom) 18 | } 19 | -------------------------------------------------------------------------------- /hooks/use-copy-to-clipboard.ts: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | 5 | export function useCopyToClipboard({ 6 | timeout = 2000, 7 | onCopy, 8 | }: { 9 | timeout?: number 10 | onCopy?: () => void 11 | } = {}) { 12 | const [isCopied, setIsCopied] = React.useState(false) 13 | 14 | const copyToClipboard = (value: string) => { 15 | if (typeof window === "undefined" || !navigator.clipboard.writeText) { 16 | return 17 | } 18 | 19 | if (!value) return 20 | 21 | navigator.clipboard.writeText(value).then(() => { 22 | setIsCopied(true) 23 | 24 | if (onCopy) { 25 | onCopy() 26 | } 27 | 28 | if (timeout !== 0) { 29 | setTimeout(() => { 30 | setIsCopied(false) 31 | }, timeout) 32 | } 33 | }, console.error) 34 | } 35 | 36 | return { isCopied, copyToClipboard } 37 | } 38 | -------------------------------------------------------------------------------- /hooks/use-is-mac.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | 3 | export function useIsMac() { 4 | const [isMac, setIsMac] = useState(true) 5 | 6 | useEffect(() => { 7 | setIsMac(navigator.platform.toUpperCase().includes("MAC")) 8 | }, []) 9 | 10 | return isMac 11 | } 12 | -------------------------------------------------------------------------------- /hooks/use-media-query.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | export function useMediaQuery(query: string) { 4 | const [value, setValue] = React.useState(false) 5 | 6 | React.useEffect(() => { 7 | function onChange(event: MediaQueryListEvent) { 8 | setValue(event.matches) 9 | } 10 | 11 | const result = matchMedia(query) 12 | result.addEventListener("change", onChange) 13 | setValue(result.matches) 14 | 15 | return () => result.removeEventListener("change", onChange) 16 | }, [query]) 17 | 18 | return value 19 | } 20 | -------------------------------------------------------------------------------- /hooks/use-meta-color.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { useTheme } from "next-themes" 3 | 4 | export const META_THEME_COLORS = { 5 | light: "#ffffff", 6 | dark: "#0a0a0a", 7 | } 8 | 9 | export function useMetaColor() { 10 | const { resolvedTheme } = useTheme() 11 | 12 | const metaColor = React.useMemo(() => { 13 | return resolvedTheme !== "dark" 14 | ? META_THEME_COLORS.light 15 | : META_THEME_COLORS.dark 16 | }, [resolvedTheme]) 17 | 18 | const setMetaColor = React.useCallback((color: string) => { 19 | document 20 | .querySelector('meta[name="theme-color"]') 21 | ?.setAttribute("content", color) 22 | }, []) 23 | 24 | return { 25 | metaColor, 26 | setMetaColor, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hooks/use-mounted.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | export function useMounted() { 4 | const [mounted, setMounted] = React.useState(false) 5 | 6 | React.useEffect(() => { 7 | setMounted(true) 8 | }, []) 9 | 10 | return mounted 11 | } 12 | -------------------------------------------------------------------------------- /hooks/use-mutation-observer.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | export const useMutationObserver = ( 4 | ref: React.RefObject, 5 | callback: MutationCallback, 6 | options: MutationObserverInit = { 7 | attributes: true, 8 | characterData: true, 9 | childList: true, 10 | subtree: true, 11 | } 12 | ) => { 13 | React.useEffect(() => { 14 | if (ref.current) { 15 | const observer = new MutationObserver(callback) 16 | observer.observe(ref.current, options) 17 | return () => observer.disconnect() 18 | } 19 | }, [ref, callback, options]) 20 | } 21 | -------------------------------------------------------------------------------- /lib/blocks.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { registryItemSchema } from "shadcn/registry" 4 | import { z } from "zod" 5 | 6 | export async function getAllBlockIds( 7 | types: z.infer["type"][] = [ 8 | "registry:block", 9 | "registry:internal", 10 | ], 11 | categories: string[] = [] 12 | ): Promise { 13 | const { Index } = await import("@/registry/__index__") 14 | const index = z.record(registryItemSchema).parse(Index) 15 | 16 | return Object.values(index) 17 | .filter( 18 | (block) => 19 | types.includes(block.type) && 20 | (categories.length === 0 || 21 | block.categories?.some((category) => 22 | categories.includes(category) 23 | )) && 24 | !block.name.startsWith("chart-") 25 | ) 26 | .map((block) => block.name) 27 | } 28 | -------------------------------------------------------------------------------- /lib/config.ts: -------------------------------------------------------------------------------- 1 | export const siteConfig = { 2 | name: "shadcn/editor", 3 | url: "https://shadcn-editor.vercel.app", 4 | ogImage: "https://shadcn-editor.vercel.app/og.jpg", 5 | description: 6 | "Lexical based text editor using shadcn/ui. Accessible. Customizable. Open Source.", 7 | links: { 8 | twitter: "https://twitter.com/htmujahid", 9 | github: "https://github.com/htmujahid/shadcn-editor", 10 | }, 11 | navItems: [ 12 | { 13 | label: "Docs", 14 | href: "/docs", 15 | }, 16 | { 17 | label: "Plugins", 18 | href: "/docs/plugins", 19 | }, 20 | { 21 | label: "Blocks", 22 | href: "/blocks", 23 | }, 24 | ], 25 | } 26 | 27 | export const META_THEME_COLORS = { 28 | light: "#ffffff", 29 | dark: "#09090b", 30 | } 31 | -------------------------------------------------------------------------------- /lib/events.ts: -------------------------------------------------------------------------------- 1 | import va from "@vercel/analytics" 2 | import { z } from "zod" 3 | 4 | const eventSchema = z.object({ 5 | name: z.enum([ 6 | "copy_npm_command", 7 | "copy_usage_import_code", 8 | "copy_usage_code", 9 | "copy_primitive_code", 10 | "copy_theme_code", 11 | "copy_block_code", 12 | "copy_chunk_code", 13 | "enable_lift_mode", 14 | "copy_chart_code", 15 | "copy_chart_theme", 16 | "copy_chart_data", 17 | "copy_color", 18 | ]), 19 | // declare type AllowedPropertyValues = string | number | boolean | null 20 | properties: z 21 | .record(z.union([z.string(), z.number(), z.boolean(), z.null()])) 22 | .optional(), 23 | }) 24 | 25 | export type Event = z.infer 26 | 27 | export function trackEvent(input: Event): void { 28 | const event = eventSchema.parse(input) 29 | if (event) { 30 | va.track(event.name, event.properties) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/fonts.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Geist_Mono as FontMono, 3 | Geist as FontSans, 4 | Inter, 5 | } from "next/font/google" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const fontSans = FontSans({ 10 | subsets: ["latin"], 11 | variable: "--font-sans", 12 | }) 13 | 14 | const fontMono = FontMono({ 15 | subsets: ["latin"], 16 | variable: "--font-mono", 17 | weight: ["400"], 18 | }) 19 | 20 | const fontInter = Inter({ 21 | subsets: ["latin"], 22 | variable: "--font-inter", 23 | }) 24 | 25 | export const fontVariables = cn( 26 | fontSans.variable, 27 | fontMono.variable, 28 | fontInter.variable 29 | ) 30 | -------------------------------------------------------------------------------- /lib/source.ts: -------------------------------------------------------------------------------- 1 | import { docs } from "@/.source" 2 | import { loader } from "fumadocs-core/source" 3 | 4 | export const source: ReturnType = loader({ 5 | baseUrl: "/docs", 6 | source: docs.toFumadocsSource(), 7 | }) 8 | -------------------------------------------------------------------------------- /lib/themes.ts: -------------------------------------------------------------------------------- 1 | export const THEMES = [ 2 | { 3 | name: "Default", 4 | value: "default", 5 | }, 6 | { 7 | name: "Neutral", 8 | value: "neutral", 9 | }, 10 | { 11 | name: "Stone", 12 | value: "stone", 13 | }, 14 | { 15 | name: "Zinc", 16 | value: "zinc", 17 | }, 18 | { 19 | name: "Gray", 20 | value: "gray", 21 | }, 22 | { 23 | name: "Slate", 24 | value: "slate", 25 | }, 26 | { 27 | name: "Scaled", 28 | value: "scaled", 29 | }, 30 | ] 31 | export type Theme = (typeof THEMES)[number] 32 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | 8 | export function absoluteUrl(path: string) { 9 | return `${process.env.NEXT_PUBLIC_APP_URL}${path}` 10 | } 11 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | } 6 | export default config 7 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/avatars/shadcn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/avatars/shadcn.jpg -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/favicon.ico -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/opengraph-image.png -------------------------------------------------------------------------------- /public/r/actions-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json", 3 | "name": "actions-plugin", 4 | "type": "registry:ui", 5 | "registryDependencies": [ 6 | "https://shadcn-editor.vercel.app/r/rich-text-editor-plugin.json" 7 | ], 8 | "files": [ 9 | { 10 | "path": "registry/new-york-v4/editor/plugins/actions/actions-plugin.tsx", 11 | "content": "export function ActionsPlugin({ children }: { children: React.ReactNode }) {\n return children\n}\n", 12 | "type": "registry:component", 13 | "target": "components/editor/plugins/actions/actions-plugin.tsx" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /public/r/horizontal-rule-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json", 3 | "name": "horizontal-rule-plugin", 4 | "type": "registry:ui", 5 | "dependencies": [ 6 | "@lexical/utils" 7 | ], 8 | "registryDependencies": [ 9 | "select", 10 | "https://shadcn-editor.vercel.app/r/toolbar-plugin.json" 11 | ], 12 | "files": [ 13 | { 14 | "path": "registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-horizontal-rule.tsx", 15 | "content": "\"use client\"\n\nimport { INSERT_HORIZONTAL_RULE_COMMAND } from \"@lexical/react/LexicalHorizontalRuleNode\"\nimport { ScissorsIcon } from \"lucide-react\"\n\nimport { useToolbarContext } from \"@/registry/new-york-v4/editor/context/toolbar-context\"\nimport { SelectItem } from \"@/registry/new-york-v4/ui/select\"\n\nexport function InsertHorizontalRule() {\n const { activeEditor } = useToolbarContext()\n\n return (\n \n activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)\n }\n className=\"\"\n >\n
\n \n Horizontal Rule\n
\n \n )\n}\n", 16 | "type": "registry:component", 17 | "target": "components/editor/plugins/toolbar/block-insert/insert-horizontal-rule.tsx" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /public/r/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json", 3 | "name": "index", 4 | "type": "registry:style", 5 | "dependencies": [ 6 | "class-variance-authority", 7 | "lucide-react" 8 | ], 9 | "devDependencies": [ 10 | "tw-animate-css" 11 | ], 12 | "registryDependencies": [ 13 | "utils" 14 | ], 15 | "files": [], 16 | "cssVars": {} 17 | } -------------------------------------------------------------------------------- /public/r/rich-text-editor-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema/registry-item.json", 3 | "name": "rich-text-editor-plugin", 4 | "type": "registry:ui", 5 | "dependencies": [ 6 | "@lexical/rich-text" 7 | ], 8 | "registryDependencies": [ 9 | "https://shadcn-editor.vercel.app/r/editor.json" 10 | ], 11 | "files": [ 12 | { 13 | "path": "registry/new-york-v4/editor/editor-ui/content-editable.tsx", 14 | "content": "import { JSX } from \"react\"\nimport { ContentEditable as LexicalContentEditable } from \"@lexical/react/LexicalContentEditable\"\n\ntype Props = {\n placeholder: string\n className?: string\n placeholderClassName?: string\n}\n\nexport function ContentEditable({\n placeholder,\n className,\n placeholderClassName,\n}: Props): JSX.Element {\n return (\n \n {placeholder}\n
\n }\n />\n )\n}\n", 15 | "type": "registry:ui", 16 | "target": "components/editor/editor-ui/content-editable.tsx" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /public/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "properties": { 5 | "style": { 6 | "type": "string", 7 | "enum": ["default", "new-york"] 8 | }, 9 | "tailwind": { 10 | "type": "object", 11 | "properties": { 12 | "config": { 13 | "type": "string" 14 | }, 15 | "css": { 16 | "type": "string" 17 | }, 18 | "baseColor": { 19 | "type": "string" 20 | }, 21 | "cssVariables": { 22 | "type": "boolean" 23 | }, 24 | "prefix": { 25 | "type": "string" 26 | } 27 | }, 28 | "required": ["config", "css", "baseColor", "cssVariables"] 29 | }, 30 | "rsc": { 31 | "type": "boolean" 32 | }, 33 | "tsx": { 34 | "type": "boolean" 35 | }, 36 | "iconLibrary": { 37 | "type": "string" 38 | }, 39 | "aliases": { 40 | "type": "object", 41 | "properties": { 42 | "utils": { 43 | "type": "string" 44 | }, 45 | "components": { 46 | "type": "string" 47 | }, 48 | "ui": { 49 | "type": "string" 50 | }, 51 | "lib": { 52 | "type": "string" 53 | }, 54 | "hooks": { 55 | "type": "string" 56 | } 57 | }, 58 | "required": ["utils", "components"] 59 | } 60 | }, 61 | "required": ["style", "tailwind", "rsc", "aliases"] 62 | } 63 | -------------------------------------------------------------------------------- /public/schema/registry.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema#", 3 | "description": "A shadcn registry of components, hooks, pages, etc.", 4 | "type": "object", 5 | "properties": { 6 | "name": { 7 | "type": "string" 8 | }, 9 | "homepage": { 10 | "type": "string" 11 | }, 12 | "items": { 13 | "type": "array", 14 | "items": { 15 | "$ref": "https://ui.shadcn.com/schema/registry-item.json" 16 | } 17 | } 18 | }, 19 | "required": ["name", "homepage", "items"], 20 | "uniqueItems": true, 21 | "minItems": 1 22 | } 23 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "display": "standalone" 17 | } 18 | -------------------------------------------------------------------------------- /public/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/htmujahid/shadcn-editor/a6ce94583bb08cd2f854b205e84419807568aab3/public/twitter-image.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /registry/index.ts: -------------------------------------------------------------------------------- 1 | import { registryItemSchema, type Registry } from "shadcn/registry" 2 | import { z } from "zod" 3 | 4 | import { blocks } from "@/registry/registry-blocks" 5 | import { charts } from "@/registry/registry-charts" 6 | import { examples } from "@/registry/registry-examples" 7 | import { hooks } from "@/registry/registry-hooks" 8 | import { internal } from "@/registry/registry-internal" 9 | import { lib } from "@/registry/registry-lib" 10 | import { themes } from "@/registry/registry-themes" 11 | import { ui } from "@/registry/registry-ui" 12 | 13 | const DEPRECATED_ITEMS = [ 14 | "toast", 15 | "toast-demo", 16 | "toast-destructive", 17 | "toast-simple", 18 | "toast-with-action", 19 | "toast-with-title", 20 | ] 21 | 22 | export const registry = { 23 | name: "shadcn/ui", 24 | homepage: "https://ui.shadcn.com", 25 | items: z.array(registryItemSchema).parse( 26 | [ 27 | { 28 | name: "index", 29 | type: "registry:style", 30 | dependencies: ["class-variance-authority", "lucide-react"], 31 | devDependencies: ["tw-animate-css"], 32 | registryDependencies: ["utils"], 33 | cssVars: {}, 34 | files: [], 35 | }, 36 | ...ui, 37 | ...blocks, 38 | ...charts, 39 | ...lib, 40 | ...hooks, 41 | ...themes, 42 | ...examples, 43 | ...internal, 44 | ].filter((item) => { 45 | return !DEPRECATED_ITEMS.includes(item.name) 46 | }) 47 | ), 48 | } satisfies Registry 49 | -------------------------------------------------------------------------------- /registry/new-york-v4/blocks/editor-00/nodes.ts: -------------------------------------------------------------------------------- 1 | import { HeadingNode, QuoteNode } from "@lexical/rich-text" 2 | import { 3 | Klass, 4 | LexicalNode, 5 | LexicalNodeReplacement, 6 | ParagraphNode, 7 | TextNode, 8 | } from "lexical" 9 | 10 | export const nodes: ReadonlyArray | LexicalNodeReplacement> = 11 | [HeadingNode, ParagraphNode, TextNode, QuoteNode] 12 | -------------------------------------------------------------------------------- /registry/new-york-v4/blocks/editor-00/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { SerializedEditorState } from "lexical" 5 | 6 | import { Editor } from "@/registry/new-york-v4/blocks/editor-00/editor" 7 | 8 | export const initialValue = { 9 | root: { 10 | children: [ 11 | { 12 | children: [ 13 | { 14 | detail: 0, 15 | format: 0, 16 | mode: "normal", 17 | style: "", 18 | text: "Hello World 🚀", 19 | type: "text", 20 | version: 1, 21 | }, 22 | ], 23 | direction: "ltr", 24 | format: "", 25 | indent: 0, 26 | type: "paragraph", 27 | version: 1, 28 | }, 29 | ], 30 | direction: "ltr", 31 | format: "", 32 | indent: 0, 33 | type: "root", 34 | version: 1, 35 | }, 36 | } as unknown as SerializedEditorState 37 | 38 | export default function EditorPage() { 39 | const [editorState, setEditorState] = 40 | useState(initialValue) 41 | return ( 42 | setEditorState(value)} 45 | /> 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /registry/new-york-v4/blocks/editor-00/plugins.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary" 3 | import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin" 4 | 5 | import { ContentEditable } from "@/registry/new-york-v4/editor/editor-ui/content-editable" 6 | 7 | export function Plugins() { 8 | const [floatingAnchorElem, setFloatingAnchorElem] = 9 | useState(null) 10 | 11 | const onRef = (_floatingAnchorElem: HTMLDivElement) => { 12 | if (_floatingAnchorElem !== null) { 13 | setFloatingAnchorElem(_floatingAnchorElem) 14 | } 15 | } 16 | 17 | return ( 18 |
19 | {/* toolbar plugins */} 20 |
21 | 24 |
25 | 26 |
27 |
28 | } 29 | ErrorBoundary={LexicalErrorBoundary} 30 | /> 31 | {/* editor plugins */} 32 |
33 | {/* actions plugins */} 34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /registry/new-york-v4/blocks/editor-x/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { SerializedEditorState } from "lexical" 5 | 6 | import { Editor } from "@/registry/new-york-v4/blocks/editor-x/editor" 7 | 8 | export const initialValue = { 9 | root: { 10 | children: [ 11 | { 12 | children: [ 13 | { 14 | detail: 0, 15 | format: 0, 16 | mode: "normal", 17 | style: "", 18 | text: "Hello World 🚀", 19 | type: "text", 20 | version: 1, 21 | }, 22 | ], 23 | direction: "ltr", 24 | format: "", 25 | indent: 0, 26 | type: "paragraph", 27 | version: 1, 28 | }, 29 | ], 30 | direction: "ltr", 31 | format: "", 32 | indent: 0, 33 | type: "root", 34 | version: 1, 35 | }, 36 | } as unknown as SerializedEditorState 37 | 38 | export default function EditorPage() { 39 | const [editorState, setEditorState] = 40 | useState(initialValue) 41 | return ( 42 | setEditorState(value)} 45 | /> 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/context/floating-link-context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useState } from "react" 2 | 3 | const Context = createContext<{ 4 | isLinkEditMode: boolean 5 | setIsLinkEditMode: (isLinkEditMode: boolean) => void 6 | }>({ 7 | isLinkEditMode: false, 8 | setIsLinkEditMode: () => {}, 9 | }) 10 | 11 | export function FloatingLinkContext({ 12 | children, 13 | }: { 14 | children: React.ReactNode 15 | }) { 16 | const [isLinkEditMode, setIsLinkEditMode] = useState(false) 17 | 18 | return ( 19 | 20 | {children} 21 | 22 | ) 23 | } 24 | 25 | export function useFloatingLinkContext() { 26 | if (!Context) { 27 | throw new Error( 28 | "useFloatingLinkContext must be used within a FloatingLinkContext" 29 | ) 30 | } 31 | return useContext(Context) 32 | } 33 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/context/toolbar-context.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { createContext, JSX, useContext } from "react" 4 | import { LexicalEditor } from "lexical" 5 | 6 | const Context = createContext<{ 7 | activeEditor: LexicalEditor 8 | $updateToolbar: () => void 9 | blockType: string 10 | setBlockType: (blockType: string) => void 11 | showModal: ( 12 | title: string, 13 | showModal: (onClose: () => void) => JSX.Element 14 | ) => void 15 | }>({ 16 | activeEditor: {} as LexicalEditor, 17 | $updateToolbar: () => {}, 18 | blockType: "paragraph", 19 | setBlockType: () => {}, 20 | showModal: () => {}, 21 | }) 22 | 23 | export function ToolbarContext({ 24 | activeEditor, 25 | $updateToolbar, 26 | blockType, 27 | setBlockType, 28 | showModal, 29 | children, 30 | }: { 31 | activeEditor: LexicalEditor 32 | $updateToolbar: () => void 33 | blockType: string 34 | setBlockType: (blockType: string) => void 35 | showModal: ( 36 | title: string, 37 | showModal: (onClose: () => void) => JSX.Element 38 | ) => void 39 | children: React.ReactNode 40 | }) { 41 | return ( 42 | 51 | {children} 52 | 53 | ) 54 | } 55 | 56 | export function useToolbarContext() { 57 | return useContext(Context) 58 | } 59 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/editor-hooks/use-debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { useMemo, useRef } from "react" 9 | import { debounce } from "lodash-es" 10 | 11 | export function useDebounce void>( 12 | fn: T, 13 | ms: number, 14 | maxWait?: number 15 | ) { 16 | const funcRef = useRef(null) 17 | funcRef.current = fn 18 | 19 | return useMemo( 20 | () => 21 | debounce( 22 | (...args: Parameters) => { 23 | if (funcRef.current) { 24 | funcRef.current(...args) 25 | } 26 | }, 27 | ms, 28 | { maxWait } 29 | ), 30 | [ms, maxWait] 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/editor-hooks/use-update-toolbar.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext" 3 | import { 4 | $getSelection, 5 | BaseSelection, 6 | COMMAND_PRIORITY_CRITICAL, 7 | SELECTION_CHANGE_COMMAND, 8 | } from "lexical" 9 | 10 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 11 | 12 | export function useUpdateToolbarHandler( 13 | callback: (selection: BaseSelection) => void 14 | ) { 15 | const [editor] = useLexicalComposerContext() 16 | const { activeEditor } = useToolbarContext() 17 | 18 | useEffect(() => { 19 | return activeEditor.registerCommand( 20 | SELECTION_CHANGE_COMMAND, 21 | () => { 22 | const selection = $getSelection() 23 | if (selection) { 24 | callback(selection) 25 | } 26 | return false 27 | }, 28 | COMMAND_PRIORITY_CRITICAL 29 | ) 30 | }, [editor, callback]) 31 | 32 | useEffect(() => { 33 | activeEditor.getEditorState().read(() => { 34 | const selection = $getSelection() 35 | if (selection) { 36 | callback(selection) 37 | } 38 | }) 39 | }, [activeEditor, callback]) 40 | } 41 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/editor-ui/content-editable.tsx: -------------------------------------------------------------------------------- 1 | import { JSX } from "react" 2 | import { ContentEditable as LexicalContentEditable } from "@lexical/react/LexicalContentEditable" 3 | 4 | type Props = { 5 | placeholder: string 6 | className?: string 7 | placeholderClassName?: string 8 | } 9 | 10 | export function ContentEditable({ 11 | placeholder, 12 | className, 13 | placeholderClassName, 14 | }: Props): JSX.Element { 15 | return ( 16 | 29 | {placeholder} 30 | 31 | } 32 | /> 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/editor-ui/excalidraw.tsx: -------------------------------------------------------------------------------- 1 | import { Excalidraw } from "@excalidraw/excalidraw" 2 | 3 | export default Excalidraw 4 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/actions/actions-plugin.tsx: -------------------------------------------------------------------------------- 1 | export function ActionsPlugin({ children }: { children: React.ReactNode }) { 2 | return children 3 | } 4 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/actions/character-limit-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { CharacterLimitPlugin as LexicalCharacterLimitPlugin } from "@lexical/react/LexicalCharacterLimitPlugin" 2 | 3 | export function CharacterLimitPlugin({ 4 | maxLength, 5 | charset, 6 | }: { 7 | maxLength: number 8 | charset: "UTF-8" | "UTF-16" 9 | }) { 10 | return ( 11 | ( 15 |
18 | {number.remainingCharacters} 19 |
20 | )} 21 | /> 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/actions/edit-mode-toggle-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext" 5 | import { LockIcon, UnlockIcon } from "lucide-react" 6 | 7 | import { Button } from "@/registry/new-york-v4/ui/button" 8 | import { 9 | Tooltip, 10 | TooltipContent, 11 | TooltipTrigger, 12 | } from "@/registry/new-york-v4/ui/tooltip" 13 | 14 | export function EditModeTogglePlugin() { 15 | const [editor] = useLexicalComposerContext() 16 | const [isEditable, setIsEditable] = useState(() => editor.isEditable()) 17 | 18 | return ( 19 | 20 | 21 | 38 | 39 | 40 | {isEditable ? "View Only Mode" : "Edit Mode"} 41 | 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/auto-link-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | */ 10 | import * as React from "react" 11 | import { JSX } from "react" 12 | import { 13 | createLinkMatcherWithRegExp, 14 | AutoLinkPlugin as LexicalAutoLinkPlugin, 15 | } from "@lexical/react/LexicalAutoLinkPlugin" 16 | 17 | const URL_REGEX = 18 | /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)(?()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/ 22 | 23 | const MATCHERS = [ 24 | createLinkMatcherWithRegExp(URL_REGEX, (text) => { 25 | return text.startsWith("http") ? text : `https://${text}` 26 | }), 27 | createLinkMatcherWithRegExp(EMAIL_REGEX, (text) => { 28 | return `mailto:${text}` 29 | }), 30 | ] 31 | 32 | export function AutoLinkPlugin(): JSX.Element { 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/code-highlight-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | */ 10 | import { JSX, useEffect } from "react" 11 | import { registerCodeHighlighting } from "@lexical/code" 12 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext" 13 | 14 | export function CodeHighlightPlugin(): JSX.Element | null { 15 | const [editor] = useLexicalComposerContext() 16 | 17 | useEffect(() => { 18 | return registerCodeHighlighting(editor) 19 | }, [editor]) 20 | 21 | return null 22 | } 23 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/default/lexical-context-menu-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { LexicalContextMenuPlugin } from "@lexical/react/LexicalContextMenuPlugin" 2 | 3 | export default LexicalContextMenuPlugin 4 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/default/lexical-typeahead-menu-plugin.ts: -------------------------------------------------------------------------------- 1 | import { LexicalTypeaheadMenuPlugin } from "@lexical/react/LexicalTypeaheadMenuPlugin" 2 | 3 | export default LexicalTypeaheadMenuPlugin 4 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/draggable-block-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { JSX, useRef } from "react" 4 | import { DraggableBlockPlugin_EXPERIMENTAL } from "@lexical/react/LexicalDraggableBlockPlugin" 5 | import { GripVerticalIcon } from "lucide-react" 6 | 7 | const DRAGGABLE_BLOCK_MENU_CLASSNAME = "draggable-block-menu" 8 | 9 | function isOnMenu(element: HTMLElement): boolean { 10 | return !!element.closest(`.${DRAGGABLE_BLOCK_MENU_CLASSNAME}`) 11 | } 12 | 13 | export function DraggableBlockPlugin({ 14 | anchorElem, 15 | }: { 16 | anchorElem: HTMLElement | null 17 | }): JSX.Element | null { 18 | const menuRef = useRef(null) 19 | const targetLineRef = useRef(null) 20 | 21 | if (!anchorElem) { 22 | return null 23 | } 24 | 25 | return ( 26 | 35 | 36 | 37 | } 38 | targetLineComponent={ 39 |
43 | } 44 | isOnMenu={isOnMenu} 45 | /> 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/embeds/figma-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | */ 10 | import { JSX, useEffect } from "react" 11 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext" 12 | import { $insertNodeToNearestRoot } from "@lexical/utils" 13 | import { COMMAND_PRIORITY_EDITOR, createCommand, LexicalCommand } from "lexical" 14 | 15 | import { 16 | $createFigmaNode, 17 | FigmaNode, 18 | } from "@/registry/new-york-v4/editor/nodes/embeds/figma-node" 19 | 20 | export const INSERT_FIGMA_COMMAND: LexicalCommand = createCommand( 21 | "INSERT_FIGMA_COMMAND" 22 | ) 23 | 24 | export function FigmaPlugin(): JSX.Element | null { 25 | const [editor] = useLexicalComposerContext() 26 | 27 | useEffect(() => { 28 | if (!editor.hasNodes([FigmaNode])) { 29 | throw new Error("FigmaPlugin: FigmaNode not registered on editor") 30 | } 31 | 32 | return editor.registerCommand( 33 | INSERT_FIGMA_COMMAND, 34 | (payload) => { 35 | const figmaNode = $createFigmaNode(payload) 36 | $insertNodeToNearestRoot(figmaNode) 37 | return true 38 | }, 39 | COMMAND_PRIORITY_EDITOR 40 | ) 41 | }, [editor]) 42 | 43 | return null 44 | } 45 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/embeds/twitter-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | */ 10 | import { JSX, useEffect } from "react" 11 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext" 12 | import { $insertNodeToNearestRoot } from "@lexical/utils" 13 | import { COMMAND_PRIORITY_EDITOR, createCommand, LexicalCommand } from "lexical" 14 | 15 | import { 16 | $createTweetNode, 17 | TweetNode, 18 | } from "@/registry/new-york-v4/editor/nodes/embeds/tweet-node" 19 | 20 | export const INSERT_TWEET_COMMAND: LexicalCommand = createCommand( 21 | "INSERT_TWEET_COMMAND" 22 | ) 23 | 24 | export function TwitterPlugin(): JSX.Element | null { 25 | const [editor] = useLexicalComposerContext() 26 | 27 | useEffect(() => { 28 | if (!editor.hasNodes([TweetNode])) { 29 | throw new Error("TwitterPlugin: TweetNode not registered on editor") 30 | } 31 | 32 | return editor.registerCommand( 33 | INSERT_TWEET_COMMAND, 34 | (payload) => { 35 | const tweetNode = $createTweetNode(payload) 36 | $insertNodeToNearestRoot(tweetNode) 37 | 38 | return true 39 | }, 40 | COMMAND_PRIORITY_EDITOR 41 | ) 42 | }, [editor]) 43 | 44 | return null 45 | } 46 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/embeds/youtube-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | */ 10 | import { JSX, useEffect } from "react" 11 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext" 12 | import { $insertNodeToNearestRoot } from "@lexical/utils" 13 | import { COMMAND_PRIORITY_EDITOR, createCommand, LexicalCommand } from "lexical" 14 | 15 | import { 16 | $createYouTubeNode, 17 | YouTubeNode, 18 | } from "@/registry/new-york-v4/editor/nodes/embeds/youtube-node" 19 | 20 | export const INSERT_YOUTUBE_COMMAND: LexicalCommand = createCommand( 21 | "INSERT_YOUTUBE_COMMAND" 22 | ) 23 | 24 | export function YouTubePlugin(): JSX.Element | null { 25 | const [editor] = useLexicalComposerContext() 26 | 27 | useEffect(() => { 28 | if (!editor.hasNodes([YouTubeNode])) { 29 | throw new Error("YouTubePlugin: YouTubeNode not registered on editor") 30 | } 31 | 32 | return editor.registerCommand( 33 | INSERT_YOUTUBE_COMMAND, 34 | (payload) => { 35 | const youTubeNode = $createYouTubeNode(payload) 36 | $insertNodeToNearestRoot(youTubeNode) 37 | 38 | return true 39 | }, 40 | COMMAND_PRIORITY_EDITOR 41 | ) 42 | }, [editor]) 43 | 44 | return null 45 | } 46 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/link-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | */ 10 | import * as React from "react" 11 | import { JSX } from "react" 12 | import { LinkPlugin as LexicalLinkPlugin } from "@lexical/react/LexicalLinkPlugin" 13 | 14 | import { validateUrl } from "@/registry/new-york-v4/editor/utils/url" 15 | 16 | export function LinkPlugin(): JSX.Element { 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/alignment-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { FORMAT_ELEMENT_COMMAND } from "lexical" 2 | import { 3 | AlignCenterIcon, 4 | AlignJustifyIcon, 5 | AlignLeftIcon, 6 | AlignRightIcon, 7 | } from "lucide-react" 8 | 9 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 10 | 11 | export function AlignmentPickerPlugin({ 12 | alignment, 13 | }: { 14 | alignment: "left" | "center" | "right" | "justify" 15 | }) { 16 | return new ComponentPickerOption(`Align ${alignment}`, { 17 | icon: , 18 | keywords: ["align", "justify", alignment], 19 | onSelect: (_, editor) => 20 | editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment), 21 | }) 22 | } 23 | 24 | function AlignIcons({ 25 | alignment, 26 | }: { 27 | alignment: "left" | "center" | "right" | "justify" 28 | }) { 29 | switch (alignment) { 30 | case "left": 31 | return 32 | case "center": 33 | return 34 | case "right": 35 | return 36 | case "justify": 37 | return 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/bulleted-list-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_UNORDERED_LIST_COMMAND } from "@lexical/list" 2 | import { ListIcon } from "lucide-react" 3 | 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function BulletedListPickerPlugin() { 7 | return new ComponentPickerOption("Bulleted List", { 8 | icon: , 9 | keywords: ["bulleted list", "unordered list", "ul"], 10 | onSelect: (_, editor) => 11 | editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/check-list-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_CHECK_LIST_COMMAND } from "@lexical/list" 2 | import { ListTodoIcon } from "lucide-react" 3 | 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function CheckListPickerPlugin() { 7 | return new ComponentPickerOption("Check List", { 8 | icon: , 9 | keywords: ["check list", "todo list"], 10 | onSelect: (_, editor) => 11 | editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/code-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { $createCodeNode } from "@lexical/code" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $getSelection, $isRangeSelection } from "lexical" 4 | import { CodeIcon } from "lucide-react" 5 | 6 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 7 | 8 | export function CodePickerPlugin() { 9 | return new ComponentPickerOption("Code", { 10 | icon: , 11 | keywords: ["javascript", "python", "js", "codeblock"], 12 | onSelect: (_, editor) => 13 | editor.update(() => { 14 | const selection = $getSelection() 15 | 16 | if ($isRangeSelection(selection)) { 17 | if (selection.isCollapsed()) { 18 | $setBlocksType(selection, () => $createCodeNode()) 19 | } else { 20 | // Will this ever happen? 21 | const textContent = selection.getTextContent() 22 | const codeNode = $createCodeNode() 23 | selection.insertNodes([codeNode]) 24 | selection.insertRawText(textContent) 25 | } 26 | } 27 | }), 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/collapsible-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { ListCollapseIcon } from "lucide-react" 2 | 3 | import { INSERT_COLLAPSIBLE_COMMAND } from "@/registry/new-york-v4/editor/plugins/collapsible-plugin" 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function CollapsiblePickerPlugin() { 7 | return new ComponentPickerOption("Collapsible", { 8 | icon: , 9 | keywords: ["collapse", "collapsible", "toggle"], 10 | onSelect: (_, editor) => 11 | editor.dispatchCommand(INSERT_COLLAPSIBLE_COMMAND, undefined), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/columns-layout-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { Columns3Icon } from "lucide-react" 2 | 3 | import { InsertLayoutDialog } from "@/registry/new-york-v4/editor/plugins/layout-plugin" 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function ColumnsLayoutPickerPlugin() { 7 | return new ComponentPickerOption("Columns Layout", { 8 | icon: , 9 | keywords: ["columns", "layout", "grid"], 10 | onSelect: (_, editor, showModal) => 11 | showModal("Insert Columns Layout", (onClose) => ( 12 | 13 | )), 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/component-picker-option.tsx: -------------------------------------------------------------------------------- 1 | import { MenuOption } from "@lexical/react/LexicalTypeaheadMenuPlugin" 2 | import { LexicalEditor } from "lexical" 3 | 4 | export class ComponentPickerOption extends MenuOption { 5 | // What shows up in the editor 6 | title: string 7 | // Icon for display 8 | icon?: JSX.Element 9 | // For extra searching. 10 | keywords: Array 11 | // TBD 12 | keyboardShortcut?: string 13 | // What happens when you select this option? 14 | onSelect: ( 15 | queryString: string, 16 | editor: LexicalEditor, 17 | showModal: ( 18 | title: string, 19 | showModal: (onClose: () => void) => JSX.Element 20 | ) => void 21 | ) => void 22 | 23 | constructor( 24 | title: string, 25 | options: { 26 | icon?: JSX.Element 27 | keywords?: Array 28 | keyboardShortcut?: string 29 | onSelect: ( 30 | queryString: string, 31 | editor: LexicalEditor, 32 | showModal: ( 33 | title: string, 34 | showModal: (onClose: () => void) => JSX.Element 35 | ) => void 36 | ) => void 37 | } 38 | ) { 39 | super(title) 40 | this.title = title 41 | this.keywords = options.keywords || [] 42 | this.icon = options.icon 43 | this.keyboardShortcut = options.keyboardShortcut 44 | this.onSelect = options.onSelect.bind(this) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/divider-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_HORIZONTAL_RULE_COMMAND } from "@lexical/react/LexicalHorizontalRuleNode" 2 | import { MinusIcon } from "lucide-react" 3 | 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function DividerPickerPlugin() { 7 | return new ComponentPickerOption("Divider", { 8 | icon: , 9 | keywords: ["horizontal rule", "divider", "hr"], 10 | onSelect: (_, editor) => 11 | editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/embeds-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_EMBED_COMMAND } from "@lexical/react/LexicalAutoEmbedPlugin" 2 | 3 | import { 4 | CustomEmbedConfig, 5 | EmbedConfigs, 6 | } from "@/registry/new-york-v4/editor/plugins/embeds/auto-embed-plugin" 7 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 8 | 9 | export function EmbedsPickerPlugin({ 10 | embed, 11 | }: { 12 | embed: "figma" | "tweet" | "youtube-video" 13 | }) { 14 | const embedConfig = EmbedConfigs.find( 15 | (config) => config.type === embed 16 | ) as CustomEmbedConfig 17 | 18 | return new ComponentPickerOption(`Embed ${embedConfig.contentName}`, { 19 | icon: embedConfig.icon, 20 | keywords: [...embedConfig.keywords, "embed"], 21 | onSelect: (_, editor) => 22 | editor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type), 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/equation-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { DiffIcon } from "lucide-react" 2 | 3 | import { InsertEquationDialog } from "@/registry/new-york-v4/editor/plugins/equations-plugin" 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function EquationPickerPlugin() { 7 | return new ComponentPickerOption("Equation", { 8 | icon: , 9 | keywords: ["equation", "latex", "math"], 10 | onSelect: (_, editor, showModal) => 11 | showModal("Insert Equation", (onClose) => ( 12 | 13 | )), 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/excalidraw-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { FrameIcon } from "lucide-react" 2 | 3 | import { INSERT_EXCALIDRAW_COMMAND } from "@/registry/new-york-v4/editor/plugins/excalidraw-plugin" 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function ExcalidrawPickerPlugin() { 7 | return new ComponentPickerOption("Excalidraw", { 8 | icon: , 9 | keywords: ["excalidraw", "diagram", "drawing"], 10 | onSelect: (_, editor) => 11 | editor.dispatchCommand(INSERT_EXCALIDRAW_COMMAND, undefined), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/heading-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { $createHeadingNode } from "@lexical/rich-text" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $getSelection, $isRangeSelection } from "lexical" 4 | import { Heading1Icon, Heading2Icon, Heading3Icon } from "lucide-react" 5 | 6 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 7 | 8 | export function HeadingPickerPlugin({ n }: { n: 1 | 2 | 3 }) { 9 | return new ComponentPickerOption(`Heading ${n}`, { 10 | icon: , 11 | keywords: ["heading", "header", `h${n}`], 12 | onSelect: (_, editor) => 13 | editor.update(() => { 14 | const selection = $getSelection() 15 | if ($isRangeSelection(selection)) { 16 | $setBlocksType(selection, () => $createHeadingNode(`h${n}`)) 17 | } 18 | }), 19 | }) 20 | } 21 | 22 | function HeadingIcons({ n }: { n: number }) { 23 | switch (n) { 24 | case 1: 25 | return 26 | case 2: 27 | return 28 | case 3: 29 | return 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/image-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { ImageIcon } from "lucide-react" 2 | 3 | import { InsertImageDialog } from "@/registry/new-york-v4/editor/plugins/images-plugin" 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function ImagePickerPlugin() { 7 | return new ComponentPickerOption("Image", { 8 | icon: , 9 | keywords: ["image", "photo", "picture", "file"], 10 | onSelect: (_, editor, showModal) => 11 | showModal("Insert Image", (onClose) => ( 12 | 13 | )), 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/numbered-list-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_ORDERED_LIST_COMMAND } from "@lexical/list" 2 | import { ListOrderedIcon } from "lucide-react" 3 | 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function NumberedListPickerPlugin() { 7 | return new ComponentPickerOption("Numbered List", { 8 | icon: , 9 | keywords: ["numbered list", "ordered list", "ol"], 10 | onSelect: (_, editor) => 11 | editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/page-break-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { ScissorsIcon } from "lucide-react" 2 | 3 | import { INSERT_PAGE_BREAK } from "@/registry/new-york-v4/editor/plugins/page-break-plugin" 4 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 5 | 6 | export function PageBreakPickerPlugin() { 7 | return new ComponentPickerOption("Page Break", { 8 | icon: , 9 | keywords: ["page break", "divider"], 10 | onSelect: (_, editor) => 11 | editor.dispatchCommand(INSERT_PAGE_BREAK, undefined), 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/paragraph-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { $setBlocksType } from "@lexical/selection" 2 | import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical" 3 | import { TextIcon } from "lucide-react" 4 | 5 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 6 | 7 | export function ParagraphPickerPlugin() { 8 | return new ComponentPickerOption("Paragraph", { 9 | icon: , 10 | keywords: ["normal", "paragraph", "p", "text"], 11 | onSelect: (_, editor) => 12 | editor.update(() => { 13 | const selection = $getSelection() 14 | if ($isRangeSelection(selection)) { 15 | $setBlocksType(selection, () => $createParagraphNode()) 16 | } 17 | }), 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/poll-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { ListChecksIcon } from "lucide-react" 2 | 3 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 4 | import { InsertPollDialog } from "@/registry/new-york-v4/editor/plugins/poll-plugin" 5 | 6 | export function PollPickerPlugin() { 7 | return new ComponentPickerOption("Poll", { 8 | icon: , 9 | keywords: ["poll", "vote"], 10 | onSelect: (_, editor, showModal) => 11 | showModal("Insert Poll", (onClose) => ( 12 | 13 | )), 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/picker/quote-picker-plugin.tsx: -------------------------------------------------------------------------------- 1 | import { $createQuoteNode } from "@lexical/rich-text" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $getSelection, $isRangeSelection } from "lexical" 4 | import { QuoteIcon } from "lucide-react" 5 | 6 | import { ComponentPickerOption } from "@/registry/new-york-v4/editor/plugins/picker/component-picker-option" 7 | 8 | export function QuotePickerPlugin() { 9 | return new ComponentPickerOption("Quote", { 10 | icon: , 11 | keywords: ["block quote"], 12 | onSelect: (_, editor) => 13 | editor.update(() => { 14 | const selection = $getSelection() 15 | if ($isRangeSelection(selection)) { 16 | $setBlocksType(selection, () => $createQuoteNode()) 17 | } 18 | }), 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-format/block-format-data.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | CodeIcon, 3 | Heading1Icon, 4 | Heading2Icon, 5 | Heading3Icon, 6 | ListIcon, 7 | ListOrderedIcon, 8 | ListTodoIcon, 9 | QuoteIcon, 10 | TextIcon, 11 | } from "lucide-react" 12 | 13 | export const blockTypeToBlockName: Record< 14 | string, 15 | { label: string; icon: React.ReactNode } 16 | > = { 17 | paragraph: { 18 | label: "Paragraph", 19 | icon: , 20 | }, 21 | h1: { 22 | label: "Heading 1", 23 | icon: , 24 | }, 25 | h2: { 26 | label: "Heading 2", 27 | icon: , 28 | }, 29 | h3: { 30 | label: "Heading 3", 31 | icon: , 32 | }, 33 | number: { 34 | label: "Numbered List", 35 | icon: , 36 | }, 37 | bullet: { 38 | label: "Bulleted List", 39 | icon: , 40 | }, 41 | check: { 42 | label: "Check List", 43 | icon: , 44 | }, 45 | code: { 46 | label: "Code Block", 47 | icon: , 48 | }, 49 | quote: { 50 | label: "Quote", 51 | icon: , 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-format/format-bulleted-list.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_UNORDERED_LIST_COMMAND } from "@lexical/list" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { blockTypeToBlockName } from "@/registry/new-york-v4/editor/plugins/toolbar/block-format/block-format-data" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | const BLOCK_FORMAT_VALUE = "bullet" 10 | 11 | export function FormatBulletedList() { 12 | const { activeEditor, blockType } = useToolbarContext() 13 | 14 | const formatParagraph = () => { 15 | activeEditor.update(() => { 16 | const selection = $getSelection() 17 | if ($isRangeSelection(selection)) { 18 | $setBlocksType(selection, () => $createParagraphNode()) 19 | } 20 | }) 21 | } 22 | 23 | const formatBulletedList = () => { 24 | if (blockType !== "number") { 25 | activeEditor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined) 26 | } else { 27 | formatParagraph() 28 | } 29 | } 30 | 31 | return ( 32 | 33 |
34 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon} 35 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label} 36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-format/format-check-list.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_CHECK_LIST_COMMAND } from "@lexical/list" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { blockTypeToBlockName } from "@/registry/new-york-v4/editor/plugins/toolbar/block-format/block-format-data" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | const BLOCK_FORMAT_VALUE = "check" 10 | 11 | export function FormatCheckList() { 12 | const { activeEditor, blockType } = useToolbarContext() 13 | 14 | const formatParagraph = () => { 15 | activeEditor.update(() => { 16 | const selection = $getSelection() 17 | if ($isRangeSelection(selection)) { 18 | $setBlocksType(selection, () => $createParagraphNode()) 19 | } 20 | }) 21 | } 22 | 23 | const formatCheckList = () => { 24 | if (blockType !== "number") { 25 | activeEditor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined) 26 | } else { 27 | formatParagraph() 28 | } 29 | } 30 | 31 | return ( 32 | 33 |
34 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon} 35 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label} 36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-format/format-heading.tsx: -------------------------------------------------------------------------------- 1 | import { $createHeadingNode, HeadingTagType } from "@lexical/rich-text" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $getSelection } from "lexical" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { blockTypeToBlockName } from "@/registry/new-york-v4/editor/plugins/toolbar/block-format/block-format-data" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function FormatHeading({ levels = [] }: { levels: HeadingTagType[] }) { 10 | const { activeEditor, blockType } = useToolbarContext() 11 | 12 | const formatHeading = (headingSize: HeadingTagType) => { 13 | if (blockType !== headingSize) { 14 | activeEditor.update(() => { 15 | const selection = $getSelection() 16 | $setBlocksType(selection, () => $createHeadingNode(headingSize)) 17 | }) 18 | } 19 | } 20 | 21 | return levels.map((level) => ( 22 | formatHeading(level)} 26 | > 27 |
28 | {blockTypeToBlockName[level].icon} 29 | {blockTypeToBlockName[level].label} 30 |
31 |
32 | )) 33 | } 34 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-format/format-numbered-list.tsx: -------------------------------------------------------------------------------- 1 | import { INSERT_ORDERED_LIST_COMMAND } from "@lexical/list" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { blockTypeToBlockName } from "@/registry/new-york-v4/editor/plugins/toolbar/block-format/block-format-data" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | const BLOCK_FORMAT_VALUE = "number" 10 | 11 | export function FormatNumberedList() { 12 | const { activeEditor, blockType } = useToolbarContext() 13 | 14 | const formatParagraph = () => { 15 | activeEditor.update(() => { 16 | const selection = $getSelection() 17 | if ($isRangeSelection(selection)) { 18 | $setBlocksType(selection, () => $createParagraphNode()) 19 | } 20 | }) 21 | } 22 | 23 | const formatNumberedList = () => { 24 | if (blockType !== "number") { 25 | activeEditor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined) 26 | } else { 27 | formatParagraph() 28 | } 29 | } 30 | 31 | return ( 32 | 33 |
34 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon} 35 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label} 36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-format/format-paragraph.tsx: -------------------------------------------------------------------------------- 1 | import { $setBlocksType } from "@lexical/selection" 2 | import { $createParagraphNode, $getSelection, $isRangeSelection } from "lexical" 3 | 4 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 5 | import { blockTypeToBlockName } from "@/registry/new-york-v4/editor/plugins/toolbar/block-format/block-format-data" 6 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 7 | 8 | const BLOCK_FORMAT_VALUE = "paragraph" 9 | 10 | export function FormatParagraph() { 11 | const { activeEditor } = useToolbarContext() 12 | 13 | const formatParagraph = () => { 14 | activeEditor.update(() => { 15 | const selection = $getSelection() 16 | if ($isRangeSelection(selection)) { 17 | $setBlocksType(selection, () => $createParagraphNode()) 18 | } 19 | }) 20 | } 21 | 22 | return ( 23 | 24 |
25 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon} 26 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label} 27 |
28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-format/format-quote.tsx: -------------------------------------------------------------------------------- 1 | import { $createQuoteNode } from "@lexical/rich-text" 2 | import { $setBlocksType } from "@lexical/selection" 3 | import { $getSelection } from "lexical" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { blockTypeToBlockName } from "@/registry/new-york-v4/editor/plugins/toolbar/block-format/block-format-data" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | const BLOCK_FORMAT_VALUE = "quote" 10 | 11 | export function FormatQuote() { 12 | const { activeEditor, blockType } = useToolbarContext() 13 | 14 | const formatQuote = () => { 15 | if (blockType !== "quote") { 16 | activeEditor.update(() => { 17 | const selection = $getSelection() 18 | $setBlocksType(selection, () => $createQuoteNode()) 19 | }) 20 | } 21 | } 22 | 23 | return ( 24 | 25 |
26 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon} 27 | {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label} 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { PlusIcon } from "lucide-react" 4 | 5 | import { useEditorModal } from "@/registry/new-york-v4/editor/editor-hooks/use-modal" 6 | import { 7 | Select, 8 | SelectContent, 9 | SelectGroup, 10 | SelectTrigger, 11 | } from "@/registry/new-york-v4/ui/select" 12 | 13 | export function BlockInsertPlugin({ children }: { children: React.ReactNode }) { 14 | const [modal] = useEditorModal() 15 | 16 | return ( 17 | <> 18 | {modal} 19 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-collapsible-container.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ChevronRightIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { INSERT_COLLAPSIBLE_COMMAND } from "@/registry/new-york-v4/editor/plugins/collapsible-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertCollapsibleContainer() { 10 | const { activeEditor } = useToolbarContext() 11 | return ( 12 | 15 | activeEditor.dispatchCommand(INSERT_COLLAPSIBLE_COMMAND, undefined) 16 | } 17 | className="" 18 | > 19 |
20 | 21 | Collapsible container 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-columns-layout.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Columns3Icon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { InsertLayoutDialog } from "@/registry/new-york-v4/editor/plugins/layout-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertColumnsLayout() { 10 | const { activeEditor, showModal } = useToolbarContext() 11 | 12 | return ( 13 | 16 | showModal("Insert Columns Layout", (onClose) => ( 17 | 18 | )) 19 | } 20 | className="" 21 | > 22 |
23 | 24 | Columns Layout 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-embeds.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { INSERT_EMBED_COMMAND } from "@lexical/react/LexicalAutoEmbedPlugin" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { EmbedConfigs } from "@/registry/new-york-v4/editor/plugins/embeds/auto-embed-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertEmbeds() { 10 | const { activeEditor } = useToolbarContext() 11 | return EmbedConfigs.map((embedConfig) => ( 12 | { 16 | activeEditor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type) 17 | }} 18 | className="" 19 | > 20 |
21 | {embedConfig.icon} 22 | {embedConfig.contentName} 23 |
24 |
25 | )) 26 | } 27 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-equation.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { DiffIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { InsertEquationDialog } from "@/registry/new-york-v4/editor/plugins/equations-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertEquation() { 10 | const { activeEditor, showModal } = useToolbarContext() 11 | 12 | return ( 13 | 16 | showModal("Insert Equation", (onClose) => ( 17 | 18 | )) 19 | } 20 | className="" 21 | > 22 |
23 | 24 | Equation 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-excalidraw.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { FrameIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { INSERT_EXCALIDRAW_COMMAND } from "@/registry/new-york-v4/editor/plugins/excalidraw-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertExcalidraw() { 10 | const { activeEditor } = useToolbarContext() 11 | return ( 12 | 15 | activeEditor.dispatchCommand(INSERT_EXCALIDRAW_COMMAND, undefined) 16 | } 17 | className="" 18 | > 19 |
20 | 21 | Excalidraw 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-horizontal-rule.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { INSERT_HORIZONTAL_RULE_COMMAND } from "@lexical/react/LexicalHorizontalRuleNode" 4 | import { ScissorsIcon } from "lucide-react" 5 | 6 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertHorizontalRule() { 10 | const { activeEditor } = useToolbarContext() 11 | 12 | return ( 13 | 16 | activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) 17 | } 18 | className="" 19 | > 20 |
21 | 22 | Horizontal Rule 23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-image.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ImageIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { InsertImageDialog } from "@/registry/new-york-v4/editor/plugins/images-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertImage() { 10 | const { activeEditor, showModal } = useToolbarContext() 11 | 12 | return ( 13 | { 16 | showModal("Insert Image", (onClose) => ( 17 | 18 | )) 19 | }} 20 | className="" 21 | > 22 |
23 | 24 | Image 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-inline-image.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { FileImageIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { InsertInlineImageDialog } from "@/registry/new-york-v4/editor/plugins/inline-image-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertInlineImage() { 10 | const { activeEditor, showModal } = useToolbarContext() 11 | 12 | return ( 13 | 16 | showModal("Insert Inline Image", (onClose) => ( 17 | 21 | )) 22 | } 23 | className="" 24 | > 25 |
26 | 27 | Inline Image 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-page-break.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { SquareSplitVerticalIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { INSERT_PAGE_BREAK } from "@/registry/new-york-v4/editor/plugins/page-break-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertPageBreak() { 10 | const { activeEditor } = useToolbarContext() 11 | 12 | return ( 13 | 16 | activeEditor.dispatchCommand(INSERT_PAGE_BREAK, undefined) 17 | } 18 | className="" 19 | > 20 |
21 | 22 | Page Break 23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-poll.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ListChecksIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { InsertPollDialog } from "@/registry/new-york-v4/editor/plugins/poll-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertPoll() { 10 | const { activeEditor, showModal } = useToolbarContext() 11 | 12 | return ( 13 | 16 | showModal("Insert Poll", (onClose) => ( 17 | 18 | )) 19 | } 20 | className="" 21 | > 22 |
23 | 24 | Poll 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/block-insert/insert-table.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { TableIcon } from "lucide-react" 4 | 5 | import { useToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 6 | import { InsertTableDialog } from "@/registry/new-york-v4/editor/plugins/table-plugin" 7 | import { SelectItem } from "@/registry/new-york-v4/ui/select" 8 | 9 | export function InsertTable() { 10 | const { activeEditor, showModal } = useToolbarContext() 11 | 12 | return ( 13 | 16 | showModal("Insert Table", (onClose) => ( 17 | 18 | )) 19 | } 20 | className="" 21 | > 22 |
23 | 24 | Table 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/plugins/toolbar/toolbar-plugin.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useEffect, useState } from "react" 4 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext" 5 | import { COMMAND_PRIORITY_CRITICAL, SELECTION_CHANGE_COMMAND } from "lexical" 6 | 7 | import { ToolbarContext } from "@/registry/new-york-v4/editor/context/toolbar-context" 8 | import { useEditorModal } from "@/registry/new-york-v4/editor/editor-hooks/use-modal" 9 | 10 | export function ToolbarPlugin({ 11 | children, 12 | }: { 13 | children: (props: { blockType: string }) => React.ReactNode 14 | }) { 15 | const [editor] = useLexicalComposerContext() 16 | 17 | const [activeEditor, setActiveEditor] = useState(editor) 18 | const [blockType, setBlockType] = useState("paragraph") 19 | 20 | const [modal, showModal] = useEditorModal() 21 | 22 | const $updateToolbar = () => {} 23 | 24 | useEffect(() => { 25 | return activeEditor.registerCommand( 26 | SELECTION_CHANGE_COMMAND, 27 | (_payload, newEditor) => { 28 | setActiveEditor(newEditor) 29 | return false 30 | }, 31 | COMMAND_PRIORITY_CRITICAL 32 | ) 33 | }, [editor]) 34 | 35 | return ( 36 | 43 | {modal} 44 | 45 | {children({ blockType })} 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/can-use-dom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | export const CAN_USE_DOM: boolean = 10 | typeof window !== "undefined" && 11 | typeof window.document !== "undefined" && 12 | typeof window.document.createElement !== "undefined" 13 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/caret-from-point.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | export function caretFromPoint( 10 | x: number, 11 | y: number 12 | ): null | { 13 | offset: number 14 | node: Node 15 | } { 16 | if (typeof document.caretRangeFromPoint !== "undefined") { 17 | const range = document.caretRangeFromPoint(x, y) 18 | if (range === null) { 19 | return null 20 | } 21 | return { 22 | node: range.startContainer, 23 | offset: range.startOffset, 24 | } 25 | // @ts-ignore 26 | } else if (document.caretPositionFromPoint !== "undefined") { 27 | // @ts-ignore FF - no types 28 | const range = document.caretPositionFromPoint(x, y) 29 | if (range === null) { 30 | return null 31 | } 32 | return { 33 | node: range.offsetNode, 34 | offset: range.offset, 35 | } 36 | } else { 37 | // Gracefully handle IE 38 | return null 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/invariant.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | // invariant(condition, message) will refine types based on "condition", and 10 | // if "condition" is false will throw an error. This function is special-cased 11 | // in flow itself, so we can't name it anything else. 12 | export function invariant( 13 | cond?: boolean, 14 | message?: string, 15 | ...args: string[] 16 | ): asserts cond { 17 | if (cond) { 18 | return 19 | } 20 | 21 | throw new Error( 22 | "Internal Lexical error: invariant() is meant to be replaced at compile " + 23 | "time. There is no runtime version. Error: " + 24 | message 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/normalize-class-names.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | export default function normalizeClassNames( 10 | ...classNames: Array 11 | ): Array { 12 | const rval = [] 13 | for (const className of classNames) { 14 | if (className && typeof className === "string") { 15 | for (const [s] of Array.from(className.matchAll(/\S+/g))) { 16 | rval.push(s) 17 | } 18 | } 19 | } 20 | return rval 21 | } 22 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/react-patches.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import React from "react" 9 | 10 | // Webpack + React 17 fails to compile on the usage of `React.startTransition` or 11 | // `React["startTransition"]` even if it's behind a feature detection of 12 | // `"startTransition" in React`. Moving this to a constant avoids the issue :/ 13 | const START_TRANSITION = "startTransition" 14 | 15 | export function startTransition(callback: () => void) { 16 | if (START_TRANSITION in React) { 17 | React[START_TRANSITION](callback) 18 | } else { 19 | callback() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/react-test-utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import * as React from "react" 9 | import * as ReactTestUtils from "react-dom/test-utils" 10 | 11 | /** 12 | * React 19 moved act from react-dom/test-utils to react 13 | * https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-react-dom-test-utils 14 | */ 15 | export const act = 16 | "act" in React ? (React.act as typeof ReactTestUtils.act) : ReactTestUtils.act 17 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/simple-diff-with-cursor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | export default function simpleDiffWithCursor( 10 | a: string, 11 | b: string, 12 | cursor: number 13 | ): { index: number; insert: string; remove: number } { 14 | const aLength = a.length 15 | const bLength = b.length 16 | let left = 0 // number of same characters counting from left 17 | let right = 0 // number of same characters counting from right 18 | // Iterate left to the right until we find a changed character 19 | // First iteration considers the current cursor position 20 | while ( 21 | left < aLength && 22 | left < bLength && 23 | a[left] === b[left] && 24 | left < cursor 25 | ) { 26 | left++ 27 | } 28 | // Iterate right to the left until we find a changed character 29 | while ( 30 | right + left < aLength && 31 | right + left < bLength && 32 | a[aLength - right - 1] === b[bLength - right - 1] 33 | ) { 34 | right++ 35 | } 36 | // Try to iterate left further to the right without caring about the current cursor position 37 | while ( 38 | right + left < aLength && 39 | right + left < bLength && 40 | a[left] === b[left] 41 | ) { 42 | left++ 43 | } 44 | return { 45 | index: left, 46 | insert: b.slice(left, bLength - right), 47 | remove: aLength - left - right, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/use-layout-effect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { useEffect, useLayoutEffect } from "react" 9 | 10 | import { CAN_USE_DOM } from "@/registry/new-york-v4/editor/shared/can-use-dom" 11 | 12 | // This workaround is no longer necessary in React 19, 13 | // but we currently support React >=17.x 14 | // https://github.com/facebook/react/pull/26395 15 | const useLayoutEffectImpl: typeof useLayoutEffect = CAN_USE_DOM 16 | ? useLayoutEffect 17 | : useEffect 18 | 19 | export default useLayoutEffectImpl 20 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/shared/warn-only-once.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | export function warnOnlyOnce(message: string) { 10 | if (process.env.NODE_ENV === "production") { 11 | return 12 | } 13 | let run = false 14 | return () => { 15 | if (!run) { 16 | console.warn(message) 17 | } 18 | run = true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/transformers/markdown-emoji-transformer.ts: -------------------------------------------------------------------------------- 1 | import { TextMatchTransformer } from "@lexical/markdown" 2 | import { $createTextNode } from "lexical" 3 | 4 | import emojiList from "@/registry/new-york-v4/editor/utils/emoji-list" 5 | 6 | export const EMOJI: TextMatchTransformer = { 7 | dependencies: [], 8 | export: () => null, 9 | importRegExp: /:([a-z0-9_]+):/, 10 | regExp: /:([a-z0-9_]+):/, 11 | replace: (textNode, [, name]) => { 12 | const emoji = emojiList.find((e) => e.aliases.includes(name))?.emoji 13 | if (emoji) { 14 | textNode.replace($createTextNode(emoji)) 15 | } 16 | }, 17 | trigger: ":", 18 | type: "text-match", 19 | } 20 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/transformers/markdown-equation-transformer.ts: -------------------------------------------------------------------------------- 1 | import { TextMatchTransformer } from "@lexical/markdown" 2 | 3 | import { 4 | $createEquationNode, 5 | $isEquationNode, 6 | EquationNode, 7 | } from "@/registry/new-york-v4/editor/nodes/equation-node" 8 | 9 | export const EQUATION: TextMatchTransformer = { 10 | dependencies: [EquationNode], 11 | export: (node) => { 12 | if (!$isEquationNode(node)) { 13 | return null 14 | } 15 | 16 | return `$${node.getEquation()}$` 17 | }, 18 | importRegExp: /\$([^$]+?)\$/, 19 | regExp: /\$([^$]+?)\$$/, 20 | replace: (textNode, match) => { 21 | const [, equation] = match 22 | const equationNode = $createEquationNode(equation, true) 23 | textNode.replace(equationNode) 24 | }, 25 | trigger: "$", 26 | type: "text-match", 27 | } 28 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/transformers/markdown-hr-transformer.ts: -------------------------------------------------------------------------------- 1 | import { ElementTransformer } from "@lexical/markdown" 2 | import { 3 | $createHorizontalRuleNode, 4 | $isHorizontalRuleNode, 5 | HorizontalRuleNode, 6 | } from "@lexical/react/LexicalHorizontalRuleNode" 7 | import { LexicalNode } from "lexical" 8 | 9 | export const HR: ElementTransformer = { 10 | dependencies: [HorizontalRuleNode], 11 | export: (node: LexicalNode) => { 12 | return $isHorizontalRuleNode(node) ? "***" : null 13 | }, 14 | regExp: /^(---|\*\*\*|___)\s?$/, 15 | replace: (parentNode, _1, _2, isImport) => { 16 | const line = $createHorizontalRuleNode() 17 | 18 | // TODO: Get rid of isImport flag 19 | if (isImport || parentNode.getNextSibling() != null) { 20 | parentNode.replace(line) 21 | } else { 22 | parentNode.insertBefore(line) 23 | } 24 | 25 | line.selectNext() 26 | }, 27 | type: "element", 28 | } 29 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/transformers/markdown-image-transformer.ts: -------------------------------------------------------------------------------- 1 | import { TextMatchTransformer } from "@lexical/markdown" 2 | 3 | import { 4 | $createImageNode, 5 | $isImageNode, 6 | ImageNode, 7 | } from "@/registry/new-york-v4/editor/nodes/image-node" 8 | 9 | export const IMAGE: TextMatchTransformer = { 10 | dependencies: [ImageNode], 11 | export: (node) => { 12 | if (!$isImageNode(node)) { 13 | return null 14 | } 15 | 16 | return `![${node.getAltText()}](${node.getSrc()})` 17 | }, 18 | importRegExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))/, 19 | regExp: /!(?:\[([^[]*)\])(?:\(([^(]+)\))$/, 20 | replace: (textNode, match) => { 21 | const [, altText, src] = match 22 | const imageNode = $createImageNode({ 23 | altText, 24 | maxWidth: 800, 25 | src, 26 | }) 27 | textNode.replace(imageNode) 28 | }, 29 | trigger: ")", 30 | type: "text-match", 31 | } 32 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/transformers/markdown-tweet-transformer.ts: -------------------------------------------------------------------------------- 1 | import { ElementTransformer } from "@lexical/markdown" 2 | 3 | import { 4 | $createTweetNode, 5 | $isTweetNode, 6 | TweetNode, 7 | } from "@/registry/new-york-v4/editor/nodes/embeds/tweet-node" 8 | 9 | export const TWEET: ElementTransformer = { 10 | dependencies: [TweetNode], 11 | export: (node) => { 12 | if (!$isTweetNode(node)) { 13 | return null 14 | } 15 | 16 | return `` 17 | }, 18 | regExp: /\s?$/, 19 | replace: (textNode, _1, match) => { 20 | const [, id] = match 21 | const tweetNode = $createTweetNode(id) 22 | textNode.replace(tweetNode) 23 | }, 24 | type: "element", 25 | } 26 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/utils/collapsible.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | export function setDomHiddenUntilFound(dom: HTMLElement): void { 10 | // @ts-expect-error 11 | dom.hidden = "until-found" 12 | } 13 | 14 | export function domOnBeforeMatch(dom: HTMLElement, callback: () => void): void { 15 | // @ts-expect-error 16 | dom.onbeforematch = callback 17 | } 18 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/utils/get-dom-range-rect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | export function getDOMRangeRect( 9 | nativeSelection: Selection, 10 | rootElement: HTMLElement 11 | ): DOMRect { 12 | const domRange = nativeSelection.getRangeAt(0) 13 | 14 | let rect 15 | 16 | if (nativeSelection.anchorNode === rootElement) { 17 | let inner = rootElement 18 | while (inner.firstElementChild != null) { 19 | inner = inner.firstElementChild as HTMLElement 20 | } 21 | rect = inner.getBoundingClientRect() 22 | } else { 23 | rect = domRange.getBoundingClientRect() 24 | } 25 | 26 | return rect 27 | } 28 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/utils/get-selected-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | import { $isAtNodeEnd } from "@lexical/selection" 9 | import { ElementNode, RangeSelection, TextNode } from "lexical" 10 | 11 | export function getSelectedNode( 12 | selection: RangeSelection 13 | ): TextNode | ElementNode { 14 | const anchor = selection.anchor 15 | const focus = selection.focus 16 | const anchorNode = selection.anchor.getNode() 17 | const focusNode = selection.focus.getNode() 18 | if (anchorNode === focusNode) { 19 | return anchorNode 20 | } 21 | const isBackward = selection.isBackward() 22 | if (isBackward) { 23 | return $isAtNodeEnd(focus) ? anchorNode : focusNode 24 | } else { 25 | return $isAtNodeEnd(anchor) ? anchorNode : focusNode 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/utils/guard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | export function isHTMLElement(x: unknown): x is HTMLElement { 9 | return x instanceof HTMLElement 10 | } 11 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/utils/is-mobile-width.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | -------------------------------------------------------------------------------- /registry/new-york-v4/editor/utils/url.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | 9 | const SUPPORTED_URL_PROTOCOLS = new Set([ 10 | "http:", 11 | "https:", 12 | "mailto:", 13 | "sms:", 14 | "tel:", 15 | ]) 16 | 17 | export function sanitizeUrl(url: string): string { 18 | try { 19 | const parsedUrl = new URL(url) 20 | // eslint-disable-next-line no-script-url 21 | if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) { 22 | return "about:blank" 23 | } 24 | } catch { 25 | return url 26 | } 27 | return url 28 | } 29 | 30 | // Source: https://stackoverflow.com/a/8234912/2013580 31 | const urlRegExp = new RegExp( 32 | /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)/ 33 | ) 34 | export function validateUrl(url: string): boolean { 35 | // TODO Fix UI for link insertion; it should never default to an invalid URL such as https://. 36 | // Maybe show a dialog where they user can type the URL before inserting it. 37 | return url === "https://" || urlRegExp.test(url) 38 | } 39 | -------------------------------------------------------------------------------- /registry/new-york-v4/hooks/use-mobile.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /registry/new-york-v4/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | function AspectRatio({ 6 | ...props 7 | }: React.ComponentProps) { 8 | return 9 | } 10 | 11 | export { AspectRatio } 12 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Avatar({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | function AvatarImage({ 25 | className, 26 | ...props 27 | }: React.ComponentProps) { 28 | return ( 29 | 34 | ) 35 | } 36 | 37 | function AvatarFallback({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ) 51 | } 52 | 53 | export { Avatar, AvatarImage, AvatarFallback } 54 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { CheckIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function Checkbox({ 10 | className, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export { Checkbox } 33 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | function Collapsible({ 6 | ...props 7 | }: React.ComponentProps) { 8 | return 9 | } 10 | 11 | function CollapsibleTrigger({ 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 19 | ) 20 | } 21 | 22 | function CollapsibleContent({ 23 | ...props 24 | }: React.ComponentProps) { 25 | return ( 26 | 30 | ) 31 | } 32 | 33 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 34 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Progress({ 9 | className, 10 | value, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 27 | 28 | ) 29 | } 30 | 31 | export { Progress } 32 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Separator({ 9 | className, 10 | orientation = "horizontal", 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ) 26 | } 27 | 28 | export { Separator } 29 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ) 11 | } 12 | 13 | export { Skeleton } 14 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner, ToasterProps } from "sonner" 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme() 8 | 9 | return ( 10 | 22 | ) 23 | } 24 | 25 | export { Toaster } 26 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitive from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Switch({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | 27 | 28 | ) 29 | } 30 | 31 | export { Switch } 32 | -------------------------------------------------------------------------------- /registry/new-york-v4/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 |