diff --git a/.changeset/great-starfishes-shake.md b/.changeset/great-starfishes-shake.md
new file mode 100644
index 0000000000..853171ee19
--- /dev/null
+++ b/.changeset/great-starfishes-shake.md
@@ -0,0 +1,6 @@
+---
+'@udecode/plate-font': patch
+---
+
+Add api `api.fontSize.setMark`.
+Add utils `toUnitLess`.
\ No newline at end of file
diff --git a/apps/www/content/docs/cn/components/changelog.mdx b/apps/www/content/docs/cn/components/changelog.mdx
index dc4c05df4d..f4318fa66c 100644
--- a/apps/www/content/docs/cn/components/changelog.mdx
+++ b/apps/www/content/docs/cn/components/changelog.mdx
@@ -10,6 +10,13 @@ toc: true
## 2024年12月 #17
+### 12月27日 #17.7
+
+- `fixed-toolbar-buttons`: 添加 `font-size-toolbar-button`
+- `floating-toolbar`: 添加 `inline-equation-toolbar-button`
+- `turn-into-dropdown-menu`: 修复:转换为其他块后,编辑器应重新获得焦点
+- `insert-dropdown-menu`: 添加 `行内公式` 和 `公式` 并修复焦点问题
+- `slash-input-element`: 添加 `公式` 和 `行内公式`
### 12月25日 #17.6
diff --git a/apps/www/content/docs/cn/font.mdx b/apps/www/content/docs/cn/font.mdx
index dc89f06e69..bc3308ff77 100644
--- a/apps/www/content/docs/cn/font.mdx
+++ b/apps/www/content/docs/cn/font.mdx
@@ -57,6 +57,36 @@ const plugins = [
### FontSizePlugin
+#### API
+
+##### editor.api.fontSize.setMark
+
+设置字体大小标记。
+
+
+
+ 要设置的字体大小值。
+
+
+
+#### Utils
+
+##### toUnitLess
+
+将字体大小值转换为无单位的值。
+
+
+
+ 要转换的字体大小值。
+
+
+
+
+
+ 无单位字体大小值。
+
+
+
### FontWeightPlugin
## API Components
diff --git a/apps/www/content/docs/en/components/changelog.mdx b/apps/www/content/docs/en/components/changelog.mdx
index 09c9285980..11172ec8ef 100644
--- a/apps/www/content/docs/en/components/changelog.mdx
+++ b/apps/www/content/docs/en/components/changelog.mdx
@@ -12,6 +12,7 @@ Use the [CLI](https://platejs.org/docs/components/cli) to install the latest ver
### December 27 #17.7
+- `fixed-toolbar-buttons`: add `font-size-toolbar-button`
- `floating-toolbar`: add `inline-equation-toolbar-button`
- `turn-into-dropdown-menu`: Fix: after turn into other block, the editor should regain focus.
- `insert-dropdown-menu`: add `inline equation` and `equation` & fix the focus issue
diff --git a/apps/www/content/docs/en/font.mdx b/apps/www/content/docs/en/font.mdx
index d4001cf990..8b3e7e87a3 100644
--- a/apps/www/content/docs/en/font.mdx
+++ b/apps/www/content/docs/en/font.mdx
@@ -57,6 +57,36 @@ const plugins = [
### FontSizePlugin
+#### API
+
+##### editor.api.fontSize.setMark
+
+Set the font size mark.
+
+
+
+ The font size value to set.
+
+
+
+#### Utils
+
+##### toUnitLess
+
+Convert a font size value to a unitless value.
+
+
+
+ The font size value to convert.
+
+
+
+
+
+ The font size value without units.
+
+
+
### FontWeightPlugin
## API Components
diff --git a/apps/www/public/r/index.json b/apps/www/public/r/index.json
index 82f3af3a92..5fafae7ff1 100644
--- a/apps/www/public/r/index.json
+++ b/apps/www/public/r/index.json
@@ -462,6 +462,33 @@
],
"type": "registry:ui"
},
+ {
+ "dependencies": [
+ "@udecode/plate-font"
+ ],
+ "doc": {
+ "description": "A toolbar control for adjusting font size.",
+ "docs": [
+ {
+ "route": "/docs/font"
+ }
+ ],
+ "examples": [
+ "list-demo"
+ ]
+ },
+ "files": [
+ {
+ "path": "plate-ui/font-size-toolbar-button.tsx",
+ "type": "registry:ui"
+ }
+ ],
+ "name": "font-size-toolbar-button",
+ "registryDependencies": [
+ "toolbar"
+ ],
+ "type": "registry:ui"
+ },
{
"dependencies": [],
"doc": {
@@ -2259,6 +2286,7 @@
"color-dropdown-menu",
"comment-toolbar-button",
"emoji-dropdown-menu",
+ "font-size-toolbar-button",
"history-toolbar-button",
"indent-list-toolbar-button",
"indent-todo-toolbar-button",
diff --git a/apps/www/public/r/styles/default/fixed-toolbar-buttons.json b/apps/www/public/r/styles/default/fixed-toolbar-buttons.json
index 74fb3d2bb8..e0a61a5ef7 100644
--- a/apps/www/public/r/styles/default/fixed-toolbar-buttons.json
+++ b/apps/www/public/r/styles/default/fixed-toolbar-buttons.json
@@ -14,7 +14,7 @@
},
"files": [
{
- "content": "'use client';\n\nimport React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\nimport {\n FontBackgroundColorPlugin,\n FontColorPlugin,\n} from '@udecode/plate-font/react';\nimport { HighlightPlugin } from '@udecode/plate-highlight/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n VideoPlugin,\n} from '@udecode/plate-media/react';\nimport {\n ArrowUpToLineIcon,\n BaselineIcon,\n BoldIcon,\n Code2Icon,\n HighlighterIcon,\n ItalicIcon,\n PaintBucketIcon,\n StrikethroughIcon,\n UnderlineIcon,\n WandSparklesIcon,\n} from 'lucide-react';\n\nimport { MoreDropdownMenu } from '@/components/plate-ui/more-dropdown-menu';\n\nimport { AIToolbarButton } from './ai-toolbar-button';\nimport { AlignDropdownMenu } from './align-dropdown-menu';\nimport { ColorDropdownMenu } from './color-dropdown-menu';\nimport { CommentToolbarButton } from './comment-toolbar-button';\nimport { EmojiDropdownMenu } from './emoji-dropdown-menu';\nimport { ExportToolbarButton } from './export-toolbar-button';\nimport { RedoToolbarButton, UndoToolbarButton } from './history-toolbar-button';\nimport {\n BulletedIndentListToolbarButton,\n NumberedIndentListToolbarButton,\n} from './indent-list-toolbar-button';\nimport { IndentTodoToolbarButton } from './indent-todo-toolbar-button';\nimport { IndentToolbarButton } from './indent-toolbar-button';\nimport { InsertDropdownMenu } from './insert-dropdown-menu';\nimport { LineHeightDropdownMenu } from './line-height-dropdown-menu';\nimport { LinkToolbarButton } from './link-toolbar-button';\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { MediaToolbarButton } from './media-toolbar-button';\nimport { ModeDropdownMenu } from './mode-dropdown-menu';\nimport { OutdentToolbarButton } from './outdent-toolbar-button';\nimport { TableDropdownMenu } from './table-dropdown-menu';\nimport { ToggleToolbarButton } from './toggle-toolbar-button';\nimport { ToolbarGroup } from './toolbar';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FixedToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n
\n {!readOnly && (\n <>\n
\n \n \n \n\n
\n \n \n \n \n\n
\n \n \n \n \n\n
\n \n \n \n\n
\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n\n
\n \n\n \n \n \n \n \n\n
\n \n \n \n \n\n
\n \n \n \n \n \n\n
\n \n \n \n \n\n
\n \n \n >\n )}\n\n
\n\n
\n \n \n \n \n \n\n
\n \n \n
\n );\n}\n",
+ "content": "'use client';\n\nimport React from 'react';\n\nimport {\n BoldPlugin,\n CodePlugin,\n ItalicPlugin,\n StrikethroughPlugin,\n UnderlinePlugin,\n} from '@udecode/plate-basic-marks/react';\nimport { useEditorReadOnly } from '@udecode/plate-common/react';\nimport {\n FontBackgroundColorPlugin,\n FontColorPlugin,\n} from '@udecode/plate-font/react';\nimport { HighlightPlugin } from '@udecode/plate-highlight/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n VideoPlugin,\n} from '@udecode/plate-media/react';\nimport {\n ArrowUpToLineIcon,\n BaselineIcon,\n BoldIcon,\n Code2Icon,\n HighlighterIcon,\n ItalicIcon,\n PaintBucketIcon,\n StrikethroughIcon,\n UnderlineIcon,\n WandSparklesIcon,\n} from 'lucide-react';\n\nimport { MoreDropdownMenu } from '@/components/plate-ui/more-dropdown-menu';\n\nimport { AIToolbarButton } from './ai-toolbar-button';\nimport { AlignDropdownMenu } from './align-dropdown-menu';\nimport { ColorDropdownMenu } from './color-dropdown-menu';\nimport { CommentToolbarButton } from './comment-toolbar-button';\nimport { EmojiDropdownMenu } from './emoji-dropdown-menu';\nimport { ExportToolbarButton } from './export-toolbar-button';\nimport { FontSizeToolbarButton } from './font-size-toolbar-button';\nimport { RedoToolbarButton, UndoToolbarButton } from './history-toolbar-button';\nimport {\n BulletedIndentListToolbarButton,\n NumberedIndentListToolbarButton,\n} from './indent-list-toolbar-button';\nimport { IndentTodoToolbarButton } from './indent-todo-toolbar-button';\nimport { IndentToolbarButton } from './indent-toolbar-button';\nimport { InsertDropdownMenu } from './insert-dropdown-menu';\nimport { LineHeightDropdownMenu } from './line-height-dropdown-menu';\nimport { LinkToolbarButton } from './link-toolbar-button';\nimport { MarkToolbarButton } from './mark-toolbar-button';\nimport { MediaToolbarButton } from './media-toolbar-button';\nimport { ModeDropdownMenu } from './mode-dropdown-menu';\nimport { OutdentToolbarButton } from './outdent-toolbar-button';\nimport { TableDropdownMenu } from './table-dropdown-menu';\nimport { ToggleToolbarButton } from './toggle-toolbar-button';\nimport { ToolbarGroup } from './toolbar';\nimport { TurnIntoDropdownMenu } from './turn-into-dropdown-menu';\n\nexport function FixedToolbarButtons() {\n const readOnly = useEditorReadOnly();\n\n return (\n \n {!readOnly && (\n <>\n
\n \n \n \n\n
\n \n \n \n \n\n
\n \n \n \n \n\n
\n \n \n \n \n\n
\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n\n
\n \n\n \n \n \n \n \n\n
\n \n \n \n \n\n
\n \n \n \n \n \n\n
\n \n \n \n \n\n
\n \n \n >\n )}\n\n
\n\n
\n \n \n \n \n \n\n
\n \n \n
\n );\n}\n",
"path": "plate-ui/fixed-toolbar-buttons.tsx",
"target": "components/plate-ui/fixed-toolbar-buttons.tsx",
"type": "registry:ui"
@@ -28,6 +28,7 @@
"color-dropdown-menu",
"comment-toolbar-button",
"emoji-dropdown-menu",
+ "font-size-toolbar-button",
"history-toolbar-button",
"indent-list-toolbar-button",
"indent-todo-toolbar-button",
diff --git a/apps/www/public/r/styles/default/font-size-toolbar-button.json b/apps/www/public/r/styles/default/font-size-toolbar-button.json
new file mode 100644
index 0000000000..2e9486665b
--- /dev/null
+++ b/apps/www/public/r/styles/default/font-size-toolbar-button.json
@@ -0,0 +1,29 @@
+{
+ "dependencies": [
+ "@udecode/plate-font"
+ ],
+ "doc": {
+ "description": "A toolbar control for adjusting font size.",
+ "docs": [
+ {
+ "route": "/docs/font"
+ }
+ ],
+ "examples": [
+ "list-demo"
+ ]
+ },
+ "files": [
+ {
+ "content": "'use client';\nimport { useState } from 'react';\n\nimport { cn } from '@udecode/cn';\nimport { type TElement, getAboveNode, getMarks } from '@udecode/plate-common';\nimport {\n focusEditor,\n useEditorPlugin,\n useEditorSelector,\n} from '@udecode/plate-common/react';\nimport { BaseFontSizePlugin, toUnitLess } from '@udecode/plate-font';\nimport { FontSizePlugin } from '@udecode/plate-font/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { Minus, Plus } from 'lucide-react';\n\nimport { Popover, PopoverContent, PopoverTrigger } from './popover';\nimport { ToolbarButton } from './toolbar';\n\nconst DEFAULT_FONT_SIZE = '16';\n\nconst FONT_SIZE_MAP = {\n [HEADING_KEYS.h1]: '36',\n [HEADING_KEYS.h2]: '24',\n [HEADING_KEYS.h3]: '20',\n} as const;\n\nconst FONT_SIZES = [\n '8',\n '9',\n '10',\n '12',\n '14',\n '16',\n '18',\n '24',\n '30',\n '36',\n '48',\n '60',\n '72',\n '96',\n] as const;\n\nexport function FontSizeToolbarButton() {\n const [inputValue, setInputValue] = useState(DEFAULT_FONT_SIZE);\n const [isFocused, setIsFocused] = useState(false);\n const { api, editor } = useEditorPlugin(BaseFontSizePlugin);\n\n const cursorFontSize = useEditorSelector((editor) => {\n const marks = getMarks(editor);\n const fontSize = marks?.[FontSizePlugin.key];\n\n if (fontSize) {\n return toUnitLess(fontSize as string);\n }\n\n const [node] =\n getAboveNode(editor, {\n at: editor.selection?.focus,\n }) || [];\n\n return node?.type && node.type in FONT_SIZE_MAP\n ? FONT_SIZE_MAP[node.type as keyof typeof FONT_SIZE_MAP]\n : DEFAULT_FONT_SIZE;\n }, []);\n\n const handleInputChange = () => {\n const newSize = toUnitLess(inputValue);\n\n if (Number.parseInt(newSize) < 1 || Number.parseInt(newSize) > 100) {\n focusEditor(editor);\n\n return;\n }\n if (newSize !== toUnitLess(cursorFontSize)) {\n api.fontSize.setMark(`${newSize}px`);\n }\n\n focusEditor(editor);\n };\n\n const handleFontSizeChange = (delta: number) => {\n const newSize = Number(displayValue) + delta;\n api.fontSize.setMark(`${newSize}px`);\n focusEditor(editor);\n };\n\n const displayValue = isFocused ? inputValue : cursorFontSize;\n\n return (\n \n
handleFontSizeChange(-1)}>\n \n \n\n
\n \n {\n setIsFocused(false);\n handleInputChange();\n }}\n onChange={(e) => setInputValue(e.target.value)}\n onFocus={() => {\n setIsFocused(true);\n setInputValue(toUnitLess(cursorFontSize));\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleInputChange();\n }\n }}\n data-plate-focus=\"true\"\n type=\"text\"\n />\n \n e.preventDefault()}\n >\n {FONT_SIZES.map((size) => (\n \n ))}\n \n \n\n
handleFontSizeChange(1)}>\n \n \n
\n );\n}\n",
+ "path": "plate-ui/font-size-toolbar-button.tsx",
+ "target": "components/plate-ui/font-size-toolbar-button.tsx",
+ "type": "registry:ui"
+ }
+ ],
+ "name": "font-size-toolbar-button",
+ "registryDependencies": [
+ "toolbar"
+ ],
+ "type": "registry:ui"
+}
\ No newline at end of file
diff --git a/apps/www/public/r/styles/default/indent-list-toolbar-button.json b/apps/www/public/r/styles/default/indent-list-toolbar-button.json
index 2f7765eb93..8ab7b8e06a 100644
--- a/apps/www/public/r/styles/default/indent-list-toolbar-button.json
+++ b/apps/www/public/r/styles/default/indent-list-toolbar-button.json
@@ -15,7 +15,7 @@
},
"files": [
{
- "content": "'use client';\n\nimport React from 'react';\n\nimport { useEditorRef, useEditorSelector } from '@udecode/plate-common/react';\nimport {\n ListStyleType,\n someIndentList,\n toggleIndentList,\n} from '@udecode/plate-indent-list';\nimport { List, ListOrdered } from 'lucide-react';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport {\n ToolbarSplitButton,\n ToolbarSplitButtonPrimary,\n ToolbarSplitButtonSecondary,\n} from './toolbar';\n\nexport function NumberedIndentListToolbarButton() {\n const editor = useEditorRef();\n const openState = useOpenState();\n\n const pressed = useEditorSelector(\n (editor) =>\n someIndentList(editor, [\n ListStyleType.Decimal,\n ListStyleType.LowerAlpha,\n ListStyleType.UpperAlpha,\n ListStyleType.LowerRoman,\n ListStyleType.UpperRoman,\n ]),\n []\n );\n\n return (\n \n {\n toggleIndentList(editor, {\n listStyleType: ListStyleType.Decimal,\n });\n }}\n data-state={pressed ? 'on' : 'off'}\n >\n \n \n\n \n \n \n \n\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Decimal,\n })\n }\n >\n Decimal (1, 2, 3)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.LowerAlpha,\n })\n }\n >\n Lower Alpha (a, b, c)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.UpperAlpha,\n })\n }\n >\n Upper Alpha (A, B, C)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.LowerRoman,\n })\n }\n >\n Lower Roman (i, ii, iii)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.UpperRoman,\n })\n }\n >\n Upper Roman (I, II, III)\n \n \n \n \n \n );\n}\n\nexport function BulletedIndentListToolbarButton() {\n const editor = useEditorRef();\n const openState = useOpenState();\n\n const pressed = useEditorSelector(\n (editor) =>\n someIndentList(editor, [\n ListStyleType.Disc,\n ListStyleType.Circle,\n ListStyleType.Square,\n ]),\n []\n );\n\n return (\n \n {\n toggleIndentList(editor, {\n listStyleType: ListStyleType.Disc,\n });\n }}\n data-state={pressed ? 'on' : 'off'}\n >\n
\n \n\n \n \n \n \n\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Disc,\n })\n }\n >\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Circle,\n })\n }\n >\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Square,\n })\n }\n >\n \n \n \n \n \n \n );\n}\n",
+ "content": "'use client';\n\nimport React from 'react';\n\nimport { useEditorRef, useEditorSelector } from '@udecode/plate-common/react';\nimport {\n ListStyleType,\n someIndentList,\n toggleIndentList,\n} from '@udecode/plate-indent-list';\nimport { List, ListOrdered } from 'lucide-react';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport {\n ToolbarSplitButton,\n ToolbarSplitButtonPrimary,\n ToolbarSplitButtonSecondary,\n} from './toolbar';\n\nexport function NumberedIndentListToolbarButton() {\n const editor = useEditorRef();\n const openState = useOpenState();\n\n const pressed = useEditorSelector(\n (editor) =>\n someIndentList(editor, [\n ListStyleType.Decimal,\n ListStyleType.LowerAlpha,\n ListStyleType.UpperAlpha,\n ListStyleType.LowerRoman,\n ListStyleType.UpperRoman,\n ]),\n []\n );\n\n return (\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Decimal,\n })\n }\n data-state={pressed ? 'on' : 'off'}\n tooltip=\"Numbered List\"\n >\n \n \n\n \n \n \n \n\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Decimal,\n })\n }\n >\n Decimal (1, 2, 3)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.LowerAlpha,\n })\n }\n >\n Lower Alpha (a, b, c)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.UpperAlpha,\n })\n }\n >\n Upper Alpha (A, B, C)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.LowerRoman,\n })\n }\n >\n Lower Roman (i, ii, iii)\n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.UpperRoman,\n })\n }\n >\n Upper Roman (I, II, III)\n \n \n \n \n \n );\n}\n\nexport function BulletedIndentListToolbarButton() {\n const editor = useEditorRef();\n const openState = useOpenState();\n\n const pressed = useEditorSelector(\n (editor) =>\n someIndentList(editor, [\n ListStyleType.Disc,\n ListStyleType.Circle,\n ListStyleType.Square,\n ]),\n []\n );\n\n return (\n \n {\n toggleIndentList(editor, {\n listStyleType: ListStyleType.Disc,\n });\n }}\n data-state={pressed ? 'on' : 'off'}\n tooltip=\"Bulleted List\"\n >\n
\n \n\n \n \n \n \n\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Disc,\n })\n }\n >\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Circle,\n })\n }\n >\n \n \n \n toggleIndentList(editor, {\n listStyleType: ListStyleType.Square,\n })\n }\n >\n \n \n \n \n \n \n );\n}\n",
"path": "plate-ui/indent-list-toolbar-button.tsx",
"target": "components/plate-ui/indent-list-toolbar-button.tsx",
"type": "registry:ui"
diff --git a/apps/www/public/r/styles/default/insert-dropdown-menu.json b/apps/www/public/r/styles/default/insert-dropdown-menu.json
index 02bd9e2dec..005df56ec7 100644
--- a/apps/www/public/r/styles/default/insert-dropdown-menu.json
+++ b/apps/www/public/r/styles/default/insert-dropdown-menu.json
@@ -21,7 +21,7 @@
},
"files": [
{
- "content": "'use client';\n\nimport React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { BlockquotePlugin } from '@udecode/plate-block-quote/react';\nimport { CodeBlockPlugin } from '@udecode/plate-code-block/react';\nimport {\n type PlateEditor,\n ParagraphPlugin,\n focusEditor,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport { DatePlugin } from '@udecode/plate-date/react';\nimport { ExcalidrawPlugin } from '@udecode/plate-excalidraw/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { TocPlugin } from '@udecode/plate-heading/react';\nimport { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react';\nimport { INDENT_LIST_KEYS, ListStyleType } from '@udecode/plate-indent-list';\nimport { LinkPlugin } from '@udecode/plate-link/react';\nimport { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react';\nimport { TablePlugin } from '@udecode/plate-table/react';\nimport { TogglePlugin } from '@udecode/plate-toggle/react';\nimport {\n CalendarIcon,\n ChevronRightIcon,\n Columns3Icon,\n FileCodeIcon,\n FilmIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n ImageIcon,\n Link2Icon,\n ListIcon,\n ListOrderedIcon,\n MinusIcon,\n PenToolIcon,\n PilcrowIcon,\n PlusIcon,\n QuoteIcon,\n SquareIcon,\n TableIcon,\n TableOfContentsIcon,\n} from 'lucide-react';\n\nimport {\n insertBlock,\n insertInlineElement,\n} from '@/components/editor/transforms';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { ToolbarButton } from './toolbar';\n\ntype Group = {\n group: string;\n items: Item[];\n};\n\ninterface Item {\n icon: React.ReactNode;\n onSelect: (editor: PlateEditor, value: string) => void;\n value: string;\n focusEditor?: boolean;\n label?: string;\n}\n\nconst groups: Group[] = [\n {\n group: 'Basic blocks',\n items: [\n {\n icon: ,\n label: 'Paragraph',\n value: ParagraphPlugin.key,\n },\n {\n icon: ,\n label: 'Heading 1',\n value: HEADING_KEYS.h1,\n },\n {\n icon: ,\n label: 'Heading 2',\n value: HEADING_KEYS.h2,\n },\n {\n icon: ,\n label: 'Heading 3',\n value: HEADING_KEYS.h3,\n },\n {\n icon: ,\n label: 'Table',\n value: TablePlugin.key,\n },\n {\n icon: ,\n label: 'Code',\n value: CodeBlockPlugin.key,\n },\n {\n icon: ,\n label: 'Quote',\n value: BlockquotePlugin.key,\n },\n {\n icon: ,\n label: 'Divider',\n value: HorizontalRulePlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Lists',\n items: [\n {\n icon: ,\n label: 'Bulleted list',\n value: ListStyleType.Disc,\n },\n {\n icon: ,\n label: 'Numbered list',\n value: ListStyleType.Decimal,\n },\n {\n icon: ,\n label: 'To-do list',\n value: INDENT_LIST_KEYS.todo,\n },\n {\n icon: ,\n label: 'Toggle list',\n value: TogglePlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Media',\n items: [\n {\n icon: ,\n label: 'Image',\n value: ImagePlugin.key,\n },\n {\n icon: ,\n label: 'Embed',\n value: MediaEmbedPlugin.key,\n },\n {\n icon: ,\n label: 'Excalidraw',\n value: ExcalidrawPlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Advanced blocks',\n items: [\n {\n icon: ,\n label: 'Table of contents',\n value: TocPlugin.key,\n },\n {\n icon: ,\n label: '3 columns',\n value: 'action_three_columns',\n },\n // {\n // focusEditor: false,\n // icon: ,\n // label: 'Equation',\n // value: EquationPlugin.key,\n // },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Inline',\n items: [\n {\n icon: ,\n label: 'Link',\n value: LinkPlugin.key,\n },\n {\n focusEditor: true,\n icon: ,\n label: 'Date',\n value: DatePlugin.key,\n },\n // {\n // focusEditor: false,\n // icon: ,\n // label: 'Inline Equation',\n // value: InlineEquationPlugin.key,\n // },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertInlineElement(editor, value);\n },\n })),\n },\n];\n\nexport function InsertDropdownMenu(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const openState = useOpenState();\n\n return (\n \n \n \n \n \n \n\n \n {groups.map(({ group, items: nestedItems }) => (\n \n {nestedItems.map(({ icon, label, value, onSelect }) => (\n {\n onSelect(editor, value);\n focusEditor(editor);\n }}\n >\n {icon}\n {label}\n \n ))}\n \n ))}\n \n \n );\n}\n",
+ "content": "'use client';\n\nimport React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { BlockquotePlugin } from '@udecode/plate-block-quote/react';\nimport { CodeBlockPlugin } from '@udecode/plate-code-block/react';\nimport {\n type PlateEditor,\n ParagraphPlugin,\n focusEditor,\n useEditorRef,\n} from '@udecode/plate-common/react';\nimport { DatePlugin } from '@udecode/plate-date/react';\nimport { ExcalidrawPlugin } from '@udecode/plate-excalidraw/react';\nimport { HEADING_KEYS } from '@udecode/plate-heading';\nimport { TocPlugin } from '@udecode/plate-heading/react';\nimport { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react';\nimport { INDENT_LIST_KEYS, ListStyleType } from '@udecode/plate-indent-list';\nimport { LinkPlugin } from '@udecode/plate-link/react';\nimport {\n EquationPlugin,\n InlineEquationPlugin,\n} from '@udecode/plate-math/react';\nimport { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react';\nimport { TablePlugin } from '@udecode/plate-table/react';\nimport { TogglePlugin } from '@udecode/plate-toggle/react';\nimport {\n CalendarIcon,\n ChevronRightIcon,\n Columns3Icon,\n FileCodeIcon,\n FilmIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n ImageIcon,\n Link2Icon,\n ListIcon,\n ListOrderedIcon,\n MinusIcon,\n PenToolIcon,\n PilcrowIcon,\n PlusIcon,\n QuoteIcon,\n RadicalIcon,\n SquareIcon,\n TableIcon,\n TableOfContentsIcon,\n} from 'lucide-react';\n\nimport {\n insertBlock,\n insertInlineElement,\n} from '@/components/editor/transforms';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { ToolbarButton } from './toolbar';\n\ntype Group = {\n group: string;\n items: Item[];\n};\n\ninterface Item {\n icon: React.ReactNode;\n onSelect: (editor: PlateEditor, value: string) => void;\n value: string;\n focusEditor?: boolean;\n label?: string;\n}\n\nconst groups: Group[] = [\n {\n group: 'Basic blocks',\n items: [\n {\n icon: ,\n label: 'Paragraph',\n value: ParagraphPlugin.key,\n },\n {\n icon: ,\n label: 'Heading 1',\n value: HEADING_KEYS.h1,\n },\n {\n icon: ,\n label: 'Heading 2',\n value: HEADING_KEYS.h2,\n },\n {\n icon: ,\n label: 'Heading 3',\n value: HEADING_KEYS.h3,\n },\n {\n icon: ,\n label: 'Table',\n value: TablePlugin.key,\n },\n {\n icon: ,\n label: 'Code',\n value: CodeBlockPlugin.key,\n },\n {\n icon: ,\n label: 'Quote',\n value: BlockquotePlugin.key,\n },\n {\n icon: ,\n label: 'Divider',\n value: HorizontalRulePlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Lists',\n items: [\n {\n icon: ,\n label: 'Bulleted list',\n value: ListStyleType.Disc,\n },\n {\n icon: ,\n label: 'Numbered list',\n value: ListStyleType.Decimal,\n },\n {\n icon: ,\n label: 'To-do list',\n value: INDENT_LIST_KEYS.todo,\n },\n {\n icon: ,\n label: 'Toggle list',\n value: TogglePlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Media',\n items: [\n {\n icon: ,\n label: 'Image',\n value: ImagePlugin.key,\n },\n {\n icon: ,\n label: 'Embed',\n value: MediaEmbedPlugin.key,\n },\n {\n icon: ,\n label: 'Excalidraw',\n value: ExcalidrawPlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Advanced blocks',\n items: [\n {\n icon: ,\n label: 'Table of contents',\n value: TocPlugin.key,\n },\n {\n icon: ,\n label: '3 columns',\n value: 'action_three_columns',\n },\n {\n focusEditor: false,\n icon: ,\n label: 'Equation',\n value: EquationPlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertBlock(editor, value);\n },\n })),\n },\n {\n group: 'Inline',\n items: [\n {\n icon: ,\n label: 'Link',\n value: LinkPlugin.key,\n },\n {\n focusEditor: true,\n icon: ,\n label: 'Date',\n value: DatePlugin.key,\n },\n {\n focusEditor: false,\n icon: ,\n label: 'Inline Equation',\n value: InlineEquationPlugin.key,\n },\n ].map((item) => ({\n ...item,\n onSelect: (editor, value) => {\n insertInlineElement(editor, value);\n },\n })),\n },\n];\n\nexport function InsertDropdownMenu(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const openState = useOpenState();\n\n return (\n \n \n \n \n \n \n\n \n {groups.map(({ group, items: nestedItems }) => (\n \n {nestedItems.map(({ icon, label, value, onSelect }) => (\n {\n onSelect(editor, value);\n focusEditor(editor);\n }}\n >\n {icon}\n {label}\n \n ))}\n \n ))}\n \n \n );\n}\n",
"path": "plate-ui/insert-dropdown-menu.tsx",
"target": "components/plate-ui/insert-dropdown-menu.tsx",
"type": "registry:ui"
diff --git a/apps/www/public/r/styles/default/media-toolbar-button.json b/apps/www/public/r/styles/default/media-toolbar-button.json
index b288486770..539ad99e6d 100644
--- a/apps/www/public/r/styles/default/media-toolbar-button.json
+++ b/apps/www/public/r/styles/default/media-toolbar-button.json
@@ -18,7 +18,7 @@
},
"files": [
{
- "content": "'use client';\n\nimport React, { useCallback, useState } from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { insertNodes, isUrl } from '@udecode/plate-common';\nimport { useEditorRef } from '@udecode/plate-common/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n VideoPlugin,\n} from '@udecode/plate-media/react';\nimport {\n AudioLinesIcon,\n FileUpIcon,\n FilmIcon,\n ImageIcon,\n LinkIcon,\n} from 'lucide-react';\nimport { toast } from 'sonner';\nimport { useFilePicker } from 'use-file-picker';\n\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from './alert-dialog';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { FloatingInput } from './input';\nimport {\n ToolbarSplitButton,\n ToolbarSplitButtonPrimary,\n ToolbarSplitButtonSecondary,\n} from './toolbar';\n\nconst MEDIA_CONFIG: Record<\n string,\n {\n accept: string[];\n icon: React.ReactNode;\n title: string;\n tooltip: string;\n }\n> = {\n [AudioPlugin.key]: {\n accept: ['audio/*'],\n icon: ,\n title: 'Insert Audio',\n tooltip: 'Audio',\n },\n [FilePlugin.key]: {\n accept: ['*'],\n icon: ,\n title: 'Insert File',\n tooltip: 'File',\n },\n [ImagePlugin.key]: {\n accept: ['image/*'],\n icon: ,\n title: 'Insert Image',\n tooltip: 'Image',\n },\n [VideoPlugin.key]: {\n accept: ['video/*'],\n icon: ,\n title: 'Insert Video',\n tooltip: 'Video',\n },\n};\n\nexport function MediaToolbarButton({\n children,\n nodeType,\n ...props\n}: DropdownMenuProps & { nodeType: string }) {\n const currentConfig = MEDIA_CONFIG[nodeType];\n\n const editor = useEditorRef();\n const openState = useOpenState();\n const [dialogOpen, setDialogOpen] = useState(false);\n\n const { openFilePicker } = useFilePicker({\n accept: currentConfig.accept,\n multiple: true,\n onFilesSelected: ({ plainFiles: updatedFiles }) => {\n (editor as any).tf.insert.media(updatedFiles);\n },\n });\n\n return (\n <>\n {\n openFilePicker();\n }}\n onKeyDown={(e) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n openState.onOpenChange(true);\n }\n }}\n pressed={openState.open}\n tooltip={currentConfig.tooltip}\n >\n \n {currentConfig.icon}\n \n\n \n \n \n \n\n e.stopPropagation()}\n align=\"start\"\n alignOffset={-32}\n >\n \n openFilePicker()}>\n {currentConfig.icon}\n Upload from computer\n \n setDialogOpen(true)}>\n \n Insert via URL\n \n \n \n \n \n\n {\n setDialogOpen(value);\n }}\n >\n \n \n \n \n >\n );\n}\n\nfunction MediaUrlDialogContent({\n currentConfig,\n nodeType,\n setOpen,\n}: {\n currentConfig: (typeof MEDIA_CONFIG)[string];\n nodeType: string;\n setOpen: (value: boolean) => void;\n}) {\n const editor = useEditorRef();\n const [url, setUrl] = useState('');\n\n const embedMedia = useCallback(() => {\n if (!isUrl(url)) return toast.error('Invalid URL');\n\n setOpen(false);\n insertNodes(editor, {\n children: [{ text: '' }],\n name: nodeType === FilePlugin.key ? url.split('/').pop() : undefined,\n type: nodeType,\n url,\n });\n }, [url, editor, nodeType, setOpen]);\n\n return (\n <>\n \n {currentConfig.title}\n \n\n \n setUrl(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') embedMedia();\n }}\n label=\"URL\"\n placeholder=\"\"\n type=\"url\"\n autoFocus\n />\n \n\n \n Cancel\n {\n e.preventDefault();\n embedMedia();\n }}\n >\n Accept\n \n \n >\n );\n}\n",
+ "content": "'use client';\n\nimport React, { useCallback, useState } from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { insertNodes, isUrl } from '@udecode/plate-common';\nimport { useEditorRef } from '@udecode/plate-common/react';\nimport {\n AudioPlugin,\n FilePlugin,\n ImagePlugin,\n VideoPlugin,\n} from '@udecode/plate-media/react';\nimport {\n AudioLinesIcon,\n FileUpIcon,\n FilmIcon,\n ImageIcon,\n LinkIcon,\n} from 'lucide-react';\nimport { toast } from 'sonner';\nimport { useFilePicker } from 'use-file-picker';\n\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from './alert-dialog';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n useOpenState,\n} from './dropdown-menu';\nimport { FloatingInput } from './input';\nimport {\n ToolbarSplitButton,\n ToolbarSplitButtonPrimary,\n ToolbarSplitButtonSecondary,\n} from './toolbar';\n\nconst MEDIA_CONFIG: Record<\n string,\n {\n accept: string[];\n icon: React.ReactNode;\n title: string;\n tooltip: string;\n }\n> = {\n [AudioPlugin.key]: {\n accept: ['audio/*'],\n icon: ,\n title: 'Insert Audio',\n tooltip: 'Audio',\n },\n [FilePlugin.key]: {\n accept: ['*'],\n icon: ,\n title: 'Insert File',\n tooltip: 'File',\n },\n [ImagePlugin.key]: {\n accept: ['image/*'],\n icon: ,\n title: 'Insert Image',\n tooltip: 'Image',\n },\n [VideoPlugin.key]: {\n accept: ['video/*'],\n icon: ,\n title: 'Insert Video',\n tooltip: 'Video',\n },\n};\n\nexport function MediaToolbarButton({\n children,\n nodeType,\n ...props\n}: DropdownMenuProps & { nodeType: string }) {\n const currentConfig = MEDIA_CONFIG[nodeType];\n\n const editor = useEditorRef();\n const openState = useOpenState();\n const [dialogOpen, setDialogOpen] = useState(false);\n\n const { openFilePicker } = useFilePicker({\n accept: currentConfig.accept,\n multiple: true,\n onFilesSelected: ({ plainFiles: updatedFiles }) => {\n (editor as any).tf.insert.media(updatedFiles);\n },\n });\n\n return (\n <>\n {\n openFilePicker();\n }}\n onKeyDown={(e) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n openState.onOpenChange(true);\n }\n }}\n pressed={openState.open}\n >\n \n {currentConfig.icon}\n \n\n \n \n \n \n\n e.stopPropagation()}\n align=\"start\"\n alignOffset={-32}\n >\n \n openFilePicker()}>\n {currentConfig.icon}\n Upload from computer\n \n setDialogOpen(true)}>\n \n Insert via URL\n \n \n \n \n \n\n {\n setDialogOpen(value);\n }}\n >\n \n \n \n \n >\n );\n}\n\nfunction MediaUrlDialogContent({\n currentConfig,\n nodeType,\n setOpen,\n}: {\n currentConfig: (typeof MEDIA_CONFIG)[string];\n nodeType: string;\n setOpen: (value: boolean) => void;\n}) {\n const editor = useEditorRef();\n const [url, setUrl] = useState('');\n\n const embedMedia = useCallback(() => {\n if (!isUrl(url)) return toast.error('Invalid URL');\n\n setOpen(false);\n insertNodes(editor, {\n children: [{ text: '' }],\n name: nodeType === FilePlugin.key ? url.split('/').pop() : undefined,\n type: nodeType,\n url,\n });\n }, [url, editor, nodeType, setOpen]);\n\n return (\n <>\n \n {currentConfig.title}\n \n\n \n setUrl(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') embedMedia();\n }}\n label=\"URL\"\n placeholder=\"\"\n type=\"url\"\n autoFocus\n />\n \n\n \n Cancel\n {\n e.preventDefault();\n embedMedia();\n }}\n >\n Accept\n \n \n >\n );\n}\n",
"path": "plate-ui/media-toolbar-button.tsx",
"target": "components/plate-ui/media-toolbar-button.tsx",
"type": "registry:ui"
diff --git a/apps/www/public/r/styles/default/toolbar.json b/apps/www/public/r/styles/default/toolbar.json
index 60d117938c..07029fd1b8 100644
--- a/apps/www/public/r/styles/default/toolbar.json
+++ b/apps/www/public/r/styles/default/toolbar.json
@@ -7,7 +7,7 @@
},
"files": [
{
- "content": "'use client';\n\nimport * as React from 'react';\n\nimport * as ToolbarPrimitive from '@radix-ui/react-toolbar';\nimport { cn, withCn, withRef, withVariants } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\nimport { ChevronDown } from 'lucide-react';\n\nimport { Separator } from './separator';\nimport { withTooltip } from './tooltip';\n\nexport const Toolbar = withCn(\n ToolbarPrimitive.Root,\n 'relative flex select-none items-center'\n);\n\nexport const ToolbarToggleGroup = withCn(\n ToolbarPrimitive.ToolbarToggleGroup,\n 'flex items-center'\n);\n\nexport const ToolbarLink = withCn(\n ToolbarPrimitive.Link,\n 'font-medium underline underline-offset-4'\n);\n\nexport const ToolbarSeparator = withCn(\n ToolbarPrimitive.Separator,\n 'mx-2 my-1 w-px shrink-0 bg-border'\n);\n\nconst toolbarButtonVariants = cva(\n cn(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg:not([data-icon])]:size-4'\n ),\n {\n defaultVariants: {\n size: 'sm',\n variant: 'default',\n },\n variants: {\n size: {\n default: 'h-10 px-3',\n lg: 'h-11 px-5',\n sm: 'h-7 px-2',\n },\n variant: {\n default:\n 'bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground',\n outline:\n 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',\n },\n },\n }\n);\n\nconst dropdownArrowVariants = cva(\n cn(\n 'inline-flex items-center justify-center rounded-r-md text-sm font-medium text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'\n ),\n {\n defaultVariants: {\n size: 'sm',\n variant: 'default',\n },\n variants: {\n size: {\n default: 'h-10 w-6',\n lg: 'h-11 w-8',\n sm: 'h-7 w-4',\n },\n variant: {\n default:\n 'bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground',\n outline:\n 'border border-l-0 border-input bg-transparent hover:bg-accent hover:text-accent-foreground',\n },\n },\n }\n);\n\nconst ToolbarButton = withTooltip(\n React.forwardRef<\n React.ElementRef,\n {\n isDropdown?: boolean;\n pressed?: boolean;\n } & Omit<\n React.ComponentPropsWithoutRef,\n 'asChild' | 'value'\n > &\n VariantProps\n >(\n (\n { children, className, isDropdown, pressed, size, variant, ...props },\n ref\n ) => {\n return typeof pressed === 'boolean' ? (\n \n \n {isDropdown ? (\n <>\n \n {children}\n
\n \n \n
\n >\n ) : (\n children\n )}\n \n \n ) : (\n \n {children}\n \n );\n }\n )\n);\nToolbarButton.displayName = 'ToolbarButton';\n\nexport { ToolbarButton };\n\nexport const ToolbarSplitButton = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n});\n\nexport const ToolbarSplitButtonPrimary = React.forwardRef<\n React.ElementRef,\n Omit, 'value'>\n>(({ children, className, size, variant, ...props }, ref) => {\n return (\n \n {children}\n \n );\n});\n\nexport const ToolbarSplitButtonSecondary = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<'span'> &\n VariantProps\n>(({ className, size, variant, ...props }, ref) => {\n return (\n e.stopPropagation()}\n role=\"button\"\n {...props}\n >\n \n \n );\n});\n\nToolbarSplitButton.displayName = 'ToolbarButton';\n\nexport const ToolbarToggleItem = withVariants(\n ToolbarPrimitive.ToggleItem,\n toolbarButtonVariants,\n ['variant', 'size']\n);\n\nexport const ToolbarGroup = withRef<'div'>(({ children, className }, ref) => {\n return (\n \n
{children}
\n\n
\n \n
\n
\n );\n});\n",
+ "content": "'use client';\n\nimport * as React from 'react';\n\nimport * as ToolbarPrimitive from '@radix-ui/react-toolbar';\nimport { cn, withCn, withRef, withVariants } from '@udecode/cn';\nimport { type VariantProps, cva } from 'class-variance-authority';\nimport { ChevronDown } from 'lucide-react';\n\nimport { Separator } from './separator';\nimport { withTooltip } from './tooltip';\n\nexport const Toolbar = withCn(\n ToolbarPrimitive.Root,\n 'relative flex select-none items-center'\n);\n\nexport const ToolbarToggleGroup = withCn(\n ToolbarPrimitive.ToolbarToggleGroup,\n 'flex items-center'\n);\n\nexport const ToolbarLink = withCn(\n ToolbarPrimitive.Link,\n 'font-medium underline underline-offset-4'\n);\n\nexport const ToolbarSeparator = withCn(\n ToolbarPrimitive.Separator,\n 'mx-2 my-1 w-px shrink-0 bg-border'\n);\n\nconst toolbarButtonVariants = cva(\n cn(\n 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg:not([data-icon])]:size-4'\n ),\n {\n defaultVariants: {\n size: 'sm',\n variant: 'default',\n },\n variants: {\n size: {\n default: 'h-10 px-3',\n lg: 'h-11 px-5',\n sm: 'h-7 px-2',\n },\n variant: {\n default:\n 'bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground',\n outline:\n 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',\n },\n },\n }\n);\n\nconst dropdownArrowVariants = cva(\n cn(\n 'inline-flex items-center justify-center rounded-r-md text-sm font-medium text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'\n ),\n {\n defaultVariants: {\n size: 'sm',\n variant: 'default',\n },\n variants: {\n size: {\n default: 'h-10 w-6',\n lg: 'h-11 w-8',\n sm: 'h-7 w-4',\n },\n variant: {\n default:\n 'bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground',\n outline:\n 'border border-l-0 border-input bg-transparent hover:bg-accent hover:text-accent-foreground',\n },\n },\n }\n);\n\nconst ToolbarButton = withTooltip(\n React.forwardRef<\n React.ElementRef,\n {\n isDropdown?: boolean;\n pressed?: boolean;\n } & Omit<\n React.ComponentPropsWithoutRef,\n 'asChild' | 'value'\n > &\n VariantProps\n >(\n (\n { children, className, isDropdown, pressed, size, variant, ...props },\n ref\n ) => {\n return typeof pressed === 'boolean' ? (\n \n \n {isDropdown ? (\n <>\n \n {children}\n
\n \n \n
\n >\n ) : (\n children\n )}\n \n \n ) : (\n \n {children}\n \n );\n }\n )\n);\nToolbarButton.displayName = 'ToolbarButton';\n\nexport { ToolbarButton };\n\nexport const ToolbarSplitButton = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ children, className, ...props }, ref) => {\n return (\n \n {children}\n \n );\n});\n\nexport const ToolbarSplitButtonPrimary = withTooltip(\n React.forwardRef<\n React.ElementRef,\n Omit, 'value'>\n >(({ children, className, size, variant, ...props }, ref) => {\n return (\n \n {children}\n \n );\n })\n);\n\nexport const ToolbarSplitButtonSecondary = React.forwardRef<\n HTMLButtonElement,\n React.ComponentPropsWithoutRef<'span'> &\n VariantProps\n>(({ className, size, variant, ...props }, ref) => {\n return (\n e.stopPropagation()}\n role=\"button\"\n {...props}\n >\n \n \n );\n});\n\nToolbarSplitButton.displayName = 'ToolbarButton';\n\nexport const ToolbarToggleItem = withVariants(\n ToolbarPrimitive.ToggleItem,\n toolbarButtonVariants,\n ['variant', 'size']\n);\n\nexport const ToolbarGroup = withRef<'div'>(({ children, className }, ref) => {\n return (\n \n
{children}
\n\n
\n \n
\n
\n );\n});\n",
"path": "plate-ui/toolbar.tsx",
"target": "components/plate-ui/toolbar.tsx",
"type": "registry:ui"
diff --git a/apps/www/src/__registry__/index.tsx b/apps/www/src/__registry__/index.tsx
index f4dbe49ae1..0b2001dcce 100644
--- a/apps/www/src/__registry__/index.tsx
+++ b/apps/www/src/__registry__/index.tsx
@@ -277,6 +277,22 @@ export const Index: Record = {
subcategory: "",
chunks: []
},
+ "font-size-toolbar-button": {
+ name: "font-size-toolbar-button",
+ description: "",
+ type: "registry:ui",
+ registryDependencies: ["toolbar"],
+ files: [{
+ path: "src/registry/default/plate-ui/font-size-toolbar-button.tsx",
+ type: "registry:ui",
+ target: ""
+ }],
+ component: React.lazy(() => import("@/registry/default/plate-ui/font-size-toolbar-button.tsx")),
+ source: "",
+ category: "",
+ subcategory: "",
+ chunks: []
+ },
"heading-element": {
name: "heading-element",
description: "",
@@ -1353,7 +1369,7 @@ export const Index: Record = {
name: "fixed-toolbar-buttons",
description: "",
type: "registry:ui",
- registryDependencies: ["toolbar","ai-toolbar-button","align-dropdown-menu","color-dropdown-menu","comment-toolbar-button","emoji-dropdown-menu","history-toolbar-button","indent-list-toolbar-button","indent-todo-toolbar-button","indent-toolbar-button","insert-dropdown-menu","line-height-dropdown-menu","link-toolbar-button","mark-toolbar-button","media-toolbar-button","mode-dropdown-menu","more-dropdown-menu","outdent-toolbar-button","table-dropdown-menu","toggle-toolbar-button","turn-into-dropdown-menu","export-toolbar-button"],
+ registryDependencies: ["toolbar","ai-toolbar-button","align-dropdown-menu","color-dropdown-menu","comment-toolbar-button","emoji-dropdown-menu","font-size-toolbar-button","history-toolbar-button","indent-list-toolbar-button","indent-todo-toolbar-button","indent-toolbar-button","insert-dropdown-menu","line-height-dropdown-menu","link-toolbar-button","mark-toolbar-button","media-toolbar-button","mode-dropdown-menu","more-dropdown-menu","outdent-toolbar-button","table-dropdown-menu","toggle-toolbar-button","turn-into-dropdown-menu","export-toolbar-button"],
files: [{
path: "src/registry/default/plate-ui/fixed-toolbar-buttons.tsx",
type: "registry:ui",
diff --git a/apps/www/src/registry/default/plate-ui/fixed-toolbar-buttons.tsx b/apps/www/src/registry/default/plate-ui/fixed-toolbar-buttons.tsx
index b9c5a2d180..c1197a3bf6 100644
--- a/apps/www/src/registry/default/plate-ui/fixed-toolbar-buttons.tsx
+++ b/apps/www/src/registry/default/plate-ui/fixed-toolbar-buttons.tsx
@@ -42,6 +42,7 @@ import { ColorDropdownMenu } from './color-dropdown-menu';
import { CommentToolbarButton } from './comment-toolbar-button';
import { EmojiDropdownMenu } from './emoji-dropdown-menu';
import { ExportToolbarButton } from './export-toolbar-button';
+import { FontSizeToolbarButton } from './font-size-toolbar-button';
import { RedoToolbarButton, UndoToolbarButton } from './history-toolbar-button';
import {
BulletedIndentListToolbarButton,
@@ -88,6 +89,7 @@ export function FixedToolbarButtons() {
+
diff --git a/apps/www/src/registry/default/plate-ui/font-size-toolbar-button.tsx b/apps/www/src/registry/default/plate-ui/font-size-toolbar-button.tsx
new file mode 100644
index 0000000000..433895716c
--- /dev/null
+++ b/apps/www/src/registry/default/plate-ui/font-size-toolbar-button.tsx
@@ -0,0 +1,150 @@
+'use client';
+import { useState } from 'react';
+
+import { cn } from '@udecode/cn';
+import { type TElement, getAboveNode, getMarks } from '@udecode/plate-common';
+import {
+ focusEditor,
+ useEditorPlugin,
+ useEditorSelector,
+} from '@udecode/plate-common/react';
+import { BaseFontSizePlugin, toUnitLess } from '@udecode/plate-font';
+import { FontSizePlugin } from '@udecode/plate-font/react';
+import { HEADING_KEYS } from '@udecode/plate-heading';
+import { Minus, Plus } from 'lucide-react';
+
+import { Popover, PopoverContent, PopoverTrigger } from './popover';
+import { ToolbarButton } from './toolbar';
+
+const DEFAULT_FONT_SIZE = '16';
+
+const FONT_SIZE_MAP = {
+ [HEADING_KEYS.h1]: '36',
+ [HEADING_KEYS.h2]: '24',
+ [HEADING_KEYS.h3]: '20',
+} as const;
+
+const FONT_SIZES = [
+ '8',
+ '9',
+ '10',
+ '12',
+ '14',
+ '16',
+ '18',
+ '24',
+ '30',
+ '36',
+ '48',
+ '60',
+ '72',
+ '96',
+] as const;
+
+export function FontSizeToolbarButton() {
+ const [inputValue, setInputValue] = useState(DEFAULT_FONT_SIZE);
+ const [isFocused, setIsFocused] = useState(false);
+ const { api, editor } = useEditorPlugin(BaseFontSizePlugin);
+
+ const cursorFontSize = useEditorSelector((editor) => {
+ const marks = getMarks(editor);
+ const fontSize = marks?.[FontSizePlugin.key];
+
+ if (fontSize) {
+ return toUnitLess(fontSize as string);
+ }
+
+ const [node] =
+ getAboveNode(editor, {
+ at: editor.selection?.focus,
+ }) || [];
+
+ return node?.type && node.type in FONT_SIZE_MAP
+ ? FONT_SIZE_MAP[node.type as keyof typeof FONT_SIZE_MAP]
+ : DEFAULT_FONT_SIZE;
+ }, []);
+
+ const handleInputChange = () => {
+ const newSize = toUnitLess(inputValue);
+
+ if (Number.parseInt(newSize) < 1 || Number.parseInt(newSize) > 100) {
+ focusEditor(editor);
+
+ return;
+ }
+ if (newSize !== toUnitLess(cursorFontSize)) {
+ api.fontSize.setMark(`${newSize}px`);
+ }
+
+ focusEditor(editor);
+ };
+
+ const handleFontSizeChange = (delta: number) => {
+ const newSize = Number(displayValue) + delta;
+ api.fontSize.setMark(`${newSize}px`);
+ focusEditor(editor);
+ };
+
+ const displayValue = isFocused ? inputValue : cursorFontSize;
+
+ return (
+
+
handleFontSizeChange(-1)}>
+
+
+
+
+
+ {
+ setIsFocused(false);
+ handleInputChange();
+ }}
+ onChange={(e) => setInputValue(e.target.value)}
+ onFocus={() => {
+ setIsFocused(true);
+ setInputValue(toUnitLess(cursorFontSize));
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleInputChange();
+ }
+ }}
+ data-plate-focus="true"
+ type="text"
+ />
+
+ e.preventDefault()}
+ >
+ {FONT_SIZES.map((size) => (
+
+ ))}
+
+
+
+
handleFontSizeChange(1)}>
+
+
+
+ );
+}
diff --git a/apps/www/src/registry/default/plate-ui/indent-list-toolbar-button.tsx b/apps/www/src/registry/default/plate-ui/indent-list-toolbar-button.tsx
index c1d809ac33..e9ead8cc3b 100644
--- a/apps/www/src/registry/default/plate-ui/indent-list-toolbar-button.tsx
+++ b/apps/www/src/registry/default/plate-ui/indent-list-toolbar-button.tsx
@@ -41,15 +41,16 @@ export function NumberedIndentListToolbarButton() {
);
return (
-
+
{
+ onClick={() =>
toggleIndentList(editor, {
listStyleType: ListStyleType.Decimal,
- });
- }}
+ })
+ }
data-state={pressed ? 'on' : 'off'}
+ tooltip="Numbered List"
>
@@ -128,7 +129,7 @@ export function BulletedIndentListToolbarButton() {
);
return (
-
+
{
@@ -137,6 +138,7 @@ export function BulletedIndentListToolbarButton() {
});
}}
data-state={pressed ? 'on' : 'off'}
+ tooltip="Bulleted List"
>
diff --git a/apps/www/src/registry/default/plate-ui/media-toolbar-button.tsx b/apps/www/src/registry/default/plate-ui/media-toolbar-button.tsx
index fa9689a1bb..25a3e8c102 100644
--- a/apps/www/src/registry/default/plate-ui/media-toolbar-button.tsx
+++ b/apps/www/src/registry/default/plate-ui/media-toolbar-button.tsx
@@ -114,9 +114,8 @@ export function MediaToolbarButton({
}
}}
pressed={openState.open}
- tooltip={currentConfig.tooltip}
>
-
+
{currentConfig.icon}
diff --git a/apps/www/src/registry/default/plate-ui/toolbar.tsx b/apps/www/src/registry/default/plate-ui/toolbar.tsx
index 1d27f55edd..38d2064918 100644
--- a/apps/www/src/registry/default/plate-ui/toolbar.tsx
+++ b/apps/www/src/registry/default/plate-ui/toolbar.tsx
@@ -170,28 +170,30 @@ export const ToolbarSplitButton = React.forwardRef<
);
});
-export const ToolbarSplitButtonPrimary = React.forwardRef<
- React.ElementRef,
- Omit, 'value'>
->(({ children, className, size, variant, ...props }, ref) => {
- return (
-
- {children}
-
- );
-});
+export const ToolbarSplitButtonPrimary = withTooltip(
+ React.forwardRef<
+ React.ElementRef,
+ Omit, 'value'>
+ >(({ children, className, size, variant, ...props }, ref) => {
+ return (
+
+ {children}
+
+ );
+ })
+);
export const ToolbarSplitButtonSecondary = React.forwardRef<
HTMLButtonElement,
diff --git a/apps/www/src/registry/registry-ui.ts b/apps/www/src/registry/registry-ui.ts
index b4c78c8ebe..635abe1c72 100644
--- a/apps/www/src/registry/registry-ui.ts
+++ b/apps/www/src/registry/registry-ui.ts
@@ -360,6 +360,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend';`,
'color-dropdown-menu',
'comment-toolbar-button',
'emoji-dropdown-menu',
+ 'font-size-toolbar-button',
'history-toolbar-button',
'indent-list-toolbar-button',
'indent-todo-toolbar-button',
@@ -1152,6 +1153,20 @@ export const uiNodes: Registry = [
registryDependencies: ['plate-element'],
type: 'registry:ui',
},
+ {
+ dependencies: ['@udecode/plate-font'],
+ doc: {
+ description: 'A toolbar control for adjusting font size.',
+ docs: [{ route: '/docs/font' }],
+ examples: ['list-demo'],
+ },
+ files: [
+ { path: 'plate-ui/font-size-toolbar-button.tsx', type: 'registry:ui' },
+ ],
+ name: 'font-size-toolbar-button',
+ registryDependencies: ['toolbar'],
+ type: 'registry:ui',
+ },
{
dependencies: [],
doc: {
diff --git a/packages/font/src/lib/BaseFontSizePlugin.ts b/packages/font/src/lib/BaseFontSizePlugin.ts
index b9ee69f873..b940d36ace 100644
--- a/packages/font/src/lib/BaseFontSizePlugin.ts
+++ b/packages/font/src/lib/BaseFontSizePlugin.ts
@@ -1,26 +1,46 @@
-import { createSlatePlugin } from '@udecode/plate-common';
+import {
+ type PluginConfig,
+ bindFirst,
+ createTSlatePlugin,
+} from '@udecode/plate-common';
-export const BaseFontSizePlugin = createSlatePlugin({
+import { setFontSize } from './utils';
+
+export type BaseFontSizeConfig = PluginConfig<
+ 'fontSize',
+ {},
+ {
+ fontSize: {
+ setMark: (fontSize: string) => void;
+ };
+ }
+>;
+
+export const BaseFontSizePlugin = createTSlatePlugin({
key: 'fontSize',
inject: {
nodeProps: {
nodeKey: 'fontSize',
},
},
-}).extend(({ type }) => ({
- parsers: {
- html: {
- deserializer: {
- isLeaf: true,
- parse: ({ element }) => ({ [type]: element.style.fontSize }),
- rules: [
- {
- validStyle: {
- fontSize: '*',
+})
+ .extend(({ type }) => ({
+ parsers: {
+ html: {
+ deserializer: {
+ isLeaf: true,
+ parse: ({ element }) => ({ [type]: element.style.fontSize }),
+ rules: [
+ {
+ validStyle: {
+ fontSize: '*',
+ },
},
- },
- ],
+ ],
+ },
},
},
- },
-}));
+ }))
+ .extendApi(({ editor }) => ({
+ setMark: bindFirst(setFontSize, editor),
+ }));
diff --git a/packages/font/src/lib/index.ts b/packages/font/src/lib/index.ts
index 3526ae15dc..93eac919ca 100644
--- a/packages/font/src/lib/index.ts
+++ b/packages/font/src/lib/index.ts
@@ -8,3 +8,4 @@ export * from './BaseFontFamilyPlugin';
export * from './BaseFontSizePlugin';
export * from './BaseFontWeightPlugin';
export * from './transforms/index';
+export * from './utils/index';
diff --git a/packages/font/src/lib/utils/index.ts b/packages/font/src/lib/utils/index.ts
new file mode 100644
index 0000000000..f4b63d8f72
--- /dev/null
+++ b/packages/font/src/lib/utils/index.ts
@@ -0,0 +1,6 @@
+/**
+ * @file Automatically generated by barrelsby.
+ */
+
+export * from './setFontSize';
+export * from './toUnitLess';
diff --git a/packages/font/src/lib/utils/setFontSize.ts b/packages/font/src/lib/utils/setFontSize.ts
new file mode 100644
index 0000000000..76a59185c8
--- /dev/null
+++ b/packages/font/src/lib/utils/setFontSize.ts
@@ -0,0 +1,9 @@
+import { type SlateEditor, setMarks } from '@udecode/plate-common';
+
+import { BaseFontSizePlugin } from '../BaseFontSizePlugin';
+
+export const setFontSize = (editor: SlateEditor, fontSize: string): void => {
+ setMarks(editor, {
+ [BaseFontSizePlugin.key]: fontSize,
+ });
+};
diff --git a/packages/font/src/lib/utils/toUnitLess.ts b/packages/font/src/lib/utils/toUnitLess.ts
new file mode 100644
index 0000000000..1cf3e8db70
--- /dev/null
+++ b/packages/font/src/lib/utils/toUnitLess.ts
@@ -0,0 +1,14 @@
+// return '0' if value not valid
+export const toUnitLess = (value: string): string => {
+ const match = /\d+/.exec(value);
+
+ if (!match) return '0';
+
+ const num = Number(match[0]);
+
+ if (value.endsWith('rem')) {
+ return (num * 16).toString();
+ }
+
+ return num.toString();
+};
diff --git a/templates/plate-playground-template/pnpm-lock.yaml b/templates/plate-playground-template/pnpm-lock.yaml
index ba579231ef..2ad82f9a31 100644
--- a/templates/plate-playground-template/pnpm-lock.yaml
+++ b/templates/plate-playground-template/pnpm-lock.yaml
@@ -7109,8 +7109,8 @@ snapshots:
'@typescript-eslint/parser': 8.18.1(eslint@8.57.1)(typescript@5.7.2)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1)
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
+ eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.2(eslint@8.57.1)
eslint-plugin-react-hooks: 5.1.0(eslint@8.57.1)
@@ -7133,7 +7133,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1):
+ eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0
@@ -7145,22 +7145,22 @@ snapshots:
is-glob: 4.0.3
stable-hash: 0.0.4
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.18.1(eslint@8.57.1)(typescript@5.7.2)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1)
+ eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -7171,7 +7171,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.16.0
is-glob: 4.0.3
diff --git a/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx b/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx
index 8ea968f6a4..8ad418eeb4 100644
--- a/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx
+++ b/templates/plate-playground-template/src/components/plate-ui/fixed-toolbar-buttons.tsx
@@ -42,6 +42,7 @@ import { ColorDropdownMenu } from './color-dropdown-menu';
import { CommentToolbarButton } from './comment-toolbar-button';
import { EmojiDropdownMenu } from './emoji-dropdown-menu';
import { ExportToolbarButton } from './export-toolbar-button';
+import { FontSizeToolbarButton } from './font-size-toolbar-button';
import { RedoToolbarButton, UndoToolbarButton } from './history-toolbar-button';
import {
BulletedIndentListToolbarButton,
@@ -88,6 +89,7 @@ export function FixedToolbarButtons() {
+
diff --git a/templates/plate-playground-template/src/components/plate-ui/font-size-toolbar-button.tsx b/templates/plate-playground-template/src/components/plate-ui/font-size-toolbar-button.tsx
new file mode 100644
index 0000000000..433895716c
--- /dev/null
+++ b/templates/plate-playground-template/src/components/plate-ui/font-size-toolbar-button.tsx
@@ -0,0 +1,150 @@
+'use client';
+import { useState } from 'react';
+
+import { cn } from '@udecode/cn';
+import { type TElement, getAboveNode, getMarks } from '@udecode/plate-common';
+import {
+ focusEditor,
+ useEditorPlugin,
+ useEditorSelector,
+} from '@udecode/plate-common/react';
+import { BaseFontSizePlugin, toUnitLess } from '@udecode/plate-font';
+import { FontSizePlugin } from '@udecode/plate-font/react';
+import { HEADING_KEYS } from '@udecode/plate-heading';
+import { Minus, Plus } from 'lucide-react';
+
+import { Popover, PopoverContent, PopoverTrigger } from './popover';
+import { ToolbarButton } from './toolbar';
+
+const DEFAULT_FONT_SIZE = '16';
+
+const FONT_SIZE_MAP = {
+ [HEADING_KEYS.h1]: '36',
+ [HEADING_KEYS.h2]: '24',
+ [HEADING_KEYS.h3]: '20',
+} as const;
+
+const FONT_SIZES = [
+ '8',
+ '9',
+ '10',
+ '12',
+ '14',
+ '16',
+ '18',
+ '24',
+ '30',
+ '36',
+ '48',
+ '60',
+ '72',
+ '96',
+] as const;
+
+export function FontSizeToolbarButton() {
+ const [inputValue, setInputValue] = useState(DEFAULT_FONT_SIZE);
+ const [isFocused, setIsFocused] = useState(false);
+ const { api, editor } = useEditorPlugin(BaseFontSizePlugin);
+
+ const cursorFontSize = useEditorSelector((editor) => {
+ const marks = getMarks(editor);
+ const fontSize = marks?.[FontSizePlugin.key];
+
+ if (fontSize) {
+ return toUnitLess(fontSize as string);
+ }
+
+ const [node] =
+ getAboveNode(editor, {
+ at: editor.selection?.focus,
+ }) || [];
+
+ return node?.type && node.type in FONT_SIZE_MAP
+ ? FONT_SIZE_MAP[node.type as keyof typeof FONT_SIZE_MAP]
+ : DEFAULT_FONT_SIZE;
+ }, []);
+
+ const handleInputChange = () => {
+ const newSize = toUnitLess(inputValue);
+
+ if (Number.parseInt(newSize) < 1 || Number.parseInt(newSize) > 100) {
+ focusEditor(editor);
+
+ return;
+ }
+ if (newSize !== toUnitLess(cursorFontSize)) {
+ api.fontSize.setMark(`${newSize}px`);
+ }
+
+ focusEditor(editor);
+ };
+
+ const handleFontSizeChange = (delta: number) => {
+ const newSize = Number(displayValue) + delta;
+ api.fontSize.setMark(`${newSize}px`);
+ focusEditor(editor);
+ };
+
+ const displayValue = isFocused ? inputValue : cursorFontSize;
+
+ return (
+
+
handleFontSizeChange(-1)}>
+
+
+
+
+
+ {
+ setIsFocused(false);
+ handleInputChange();
+ }}
+ onChange={(e) => setInputValue(e.target.value)}
+ onFocus={() => {
+ setIsFocused(true);
+ setInputValue(toUnitLess(cursorFontSize));
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleInputChange();
+ }
+ }}
+ data-plate-focus="true"
+ type="text"
+ />
+
+ e.preventDefault()}
+ >
+ {FONT_SIZES.map((size) => (
+
+ ))}
+
+
+
+
handleFontSizeChange(1)}>
+
+
+
+ );
+}
diff --git a/templates/plate-playground-template/src/components/plate-ui/indent-list-toolbar-button.tsx b/templates/plate-playground-template/src/components/plate-ui/indent-list-toolbar-button.tsx
index c1d809ac33..e9ead8cc3b 100644
--- a/templates/plate-playground-template/src/components/plate-ui/indent-list-toolbar-button.tsx
+++ b/templates/plate-playground-template/src/components/plate-ui/indent-list-toolbar-button.tsx
@@ -41,15 +41,16 @@ export function NumberedIndentListToolbarButton() {
);
return (
-
+
{
+ onClick={() =>
toggleIndentList(editor, {
listStyleType: ListStyleType.Decimal,
- });
- }}
+ })
+ }
data-state={pressed ? 'on' : 'off'}
+ tooltip="Numbered List"
>
@@ -128,7 +129,7 @@ export function BulletedIndentListToolbarButton() {
);
return (
-
+
{
@@ -137,6 +138,7 @@ export function BulletedIndentListToolbarButton() {
});
}}
data-state={pressed ? 'on' : 'off'}
+ tooltip="Bulleted List"
>
diff --git a/templates/plate-playground-template/src/components/plate-ui/insert-dropdown-menu.tsx b/templates/plate-playground-template/src/components/plate-ui/insert-dropdown-menu.tsx
index b53ca0a4eb..e9ac6a5fa5 100644
--- a/templates/plate-playground-template/src/components/plate-ui/insert-dropdown-menu.tsx
+++ b/templates/plate-playground-template/src/components/plate-ui/insert-dropdown-menu.tsx
@@ -19,6 +19,10 @@ import { TocPlugin } from '@udecode/plate-heading/react';
import { HorizontalRulePlugin } from '@udecode/plate-horizontal-rule/react';
import { INDENT_LIST_KEYS, ListStyleType } from '@udecode/plate-indent-list';
import { LinkPlugin } from '@udecode/plate-link/react';
+import {
+ EquationPlugin,
+ InlineEquationPlugin,
+} from '@udecode/plate-math/react';
import { ImagePlugin, MediaEmbedPlugin } from '@udecode/plate-media/react';
import { TablePlugin } from '@udecode/plate-table/react';
import { TogglePlugin } from '@udecode/plate-toggle/react';
@@ -40,6 +44,7 @@ import {
PilcrowIcon,
PlusIcon,
QuoteIcon,
+ RadicalIcon,
SquareIcon,
TableIcon,
TableOfContentsIcon,
@@ -192,12 +197,12 @@ const groups: Group[] = [
label: '3 columns',
value: 'action_three_columns',
},
- // {
- // focusEditor: false,
- // icon: ,
- // label: 'Equation',
- // value: EquationPlugin.key,
- // },
+ {
+ focusEditor: false,
+ icon: ,
+ label: 'Equation',
+ value: EquationPlugin.key,
+ },
].map((item) => ({
...item,
onSelect: (editor, value) => {
@@ -219,12 +224,12 @@ const groups: Group[] = [
label: 'Date',
value: DatePlugin.key,
},
- // {
- // focusEditor: false,
- // icon: ,
- // label: 'Inline Equation',
- // value: InlineEquationPlugin.key,
- // },
+ {
+ focusEditor: false,
+ icon: ,
+ label: 'Inline Equation',
+ value: InlineEquationPlugin.key,
+ },
].map((item) => ({
...item,
onSelect: (editor, value) => {
diff --git a/templates/plate-playground-template/src/components/plate-ui/media-toolbar-button.tsx b/templates/plate-playground-template/src/components/plate-ui/media-toolbar-button.tsx
index fa9689a1bb..25a3e8c102 100644
--- a/templates/plate-playground-template/src/components/plate-ui/media-toolbar-button.tsx
+++ b/templates/plate-playground-template/src/components/plate-ui/media-toolbar-button.tsx
@@ -114,9 +114,8 @@ export function MediaToolbarButton({
}
}}
pressed={openState.open}
- tooltip={currentConfig.tooltip}
>
-
+
{currentConfig.icon}
diff --git a/templates/plate-playground-template/src/components/plate-ui/toolbar.tsx b/templates/plate-playground-template/src/components/plate-ui/toolbar.tsx
index 1d27f55edd..38d2064918 100644
--- a/templates/plate-playground-template/src/components/plate-ui/toolbar.tsx
+++ b/templates/plate-playground-template/src/components/plate-ui/toolbar.tsx
@@ -170,28 +170,30 @@ export const ToolbarSplitButton = React.forwardRef<
);
});
-export const ToolbarSplitButtonPrimary = React.forwardRef<
- React.ElementRef,
- Omit, 'value'>
->(({ children, className, size, variant, ...props }, ref) => {
- return (
-
- {children}
-
- );
-});
+export const ToolbarSplitButtonPrimary = withTooltip(
+ React.forwardRef<
+ React.ElementRef,
+ Omit, 'value'>
+ >(({ children, className, size, variant, ...props }, ref) => {
+ return (
+
+ {children}
+
+ );
+ })
+);
export const ToolbarSplitButtonSecondary = React.forwardRef<
HTMLButtonElement,