From fc131a232957e3257d7ab82167f5229e7efa75e8 Mon Sep 17 00:00:00 2001 From: Petyo Ivanov Date: Fri, 17 Nov 2023 17:14:48 +0200 Subject: [PATCH] feat: allow icon customization --- package-lock.json | 78 +++++++--- package.json | 1 + src/MDXEditor.tsx | 14 +- .../{emergency_home.svg => admonition.svg} | 0 src/icons/arrow_drop_down.svg | 14 +- src/icons/arrow_right.svg | 8 - src/icons/{delete.svg => delete_big.svg} | 0 src/icons/{deployed_code.svg => sandpack.svg} | 0 src/plugins/core/Icon.tsx | 143 ++++++++++++++++++ src/plugins/core/PropertyPopover.tsx | 5 +- src/plugins/core/index.ts | 13 +- src/plugins/core/ui/DownshiftAutoComplete.tsx | 5 +- src/plugins/frontmatter/FrontmatterEditor.tsx | 12 +- src/plugins/image/ImageEditor.tsx | 5 +- src/plugins/link-dialog/LinkDialog.tsx | 25 ++- src/plugins/table/TableEditor.tsx | 52 +++---- .../components/BoldItalicUnderlineToggles.tsx | 10 +- src/plugins/toolbar/components/CodeToggle.tsx | 5 +- src/plugins/toolbar/components/CreateLink.tsx | 4 +- .../components/DiffSourceToggleWrapper.tsx | 10 +- .../toolbar/components/InsertAdmonition.tsx | 4 +- .../toolbar/components/InsertCodeBlock.tsx | 4 +- .../toolbar/components/InsertFrontmatter.tsx | 4 +- .../toolbar/components/InsertImage.tsx | 7 +- .../toolbar/components/InsertSandpack.tsx | 4 +- .../toolbar/components/InsertTable.tsx | 4 +- .../components/InsertThematicBreak.tsx | 4 +- .../toolbar/components/ListsToggle.tsx | 10 +- .../toolbar/components/ShowSandpackInfo.tsx | 9 +- src/plugins/toolbar/components/UndoRedo.tsx | 7 +- .../toolbar/primitives/DialogButton.tsx | 12 +- .../toolbar/primitives/TooltipWrap.tsx | 2 +- src/plugins/toolbar/primitives/select.tsx | 13 +- src/styles/ui.module.css | 87 ++++++----- tsconfig.json | 5 + vite.config.ts | 3 +- 36 files changed, 389 insertions(+), 194 deletions(-) rename src/icons/{emergency_home.svg => admonition.svg} (100%) delete mode 100644 src/icons/arrow_right.svg rename src/icons/{delete.svg => delete_big.svg} (100%) rename src/icons/{deployed_code.svg => sandpack.svg} (100%) create mode 100644 src/plugins/core/Icon.tsx diff --git a/package-lock.json b/package-lock.json index 25a0eee6..bbd7f208 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,6 +116,7 @@ "vite": "^4.3.9", "vite-plugin-dts": "^2.3.0", "vite-plugin-svgr": "^3.2.0", + "vite-tsconfig-paths": "^4.2.1", "vitest": "^0.31.1" }, "engines": { @@ -1742,6 +1743,35 @@ "node": ">=12" } }, + "node_modules/@ladle/react/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@ladle/react/node_modules/vite-tsconfig-paths": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-3.6.0.tgz", + "integrity": "sha512-UfsPYonxLqPD633X8cWcPFVuYzx/CMNHAjZTasYwX69sXpa4gNmQkR0XCjj82h7zhLGdTWagMjC1qfb9S+zv0A==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "recrawl-sync": "^2.0.3", + "tsconfig-paths": "^4.0.0" + }, + "peerDependencies": { + "vite": ">2.0.0-0" + } + }, "node_modules/@lexical/clipboard": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.12.2.tgz", @@ -19797,6 +19827,26 @@ "code-block-writer": "^12.0.0" } }, + "node_modules/tsconfck": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-2.1.2.tgz", + "integrity": "sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^14.13.1 || ^16 || >=18" + }, + "peerDependencies": { + "typescript": "^4.3.5 || ^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -20732,32 +20782,22 @@ } }, "node_modules/vite-tsconfig-paths": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-3.6.0.tgz", - "integrity": "sha512-UfsPYonxLqPD633X8cWcPFVuYzx/CMNHAjZTasYwX69sXpa4gNmQkR0XCjj82h7zhLGdTWagMjC1qfb9S+zv0A==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.1.tgz", + "integrity": "sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==", "dev": true, "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", - "recrawl-sync": "^2.0.3", - "tsconfig-paths": "^4.0.0" + "tsconfck": "^2.1.0" }, "peerDependencies": { - "vite": ">2.0.0-0" - } - }, - "node_modules/vite-tsconfig-paths/node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "vite": "*" }, - "engines": { - "node": ">=6" + "peerDependenciesMeta": { + "vite": { + "optional": true + } } }, "node_modules/vitest": { diff --git a/package.json b/package.json index 995a3404..2da3de1f 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ "vite": "^4.3.9", "vite-plugin-dts": "^2.3.0", "vite-plugin-svgr": "^3.2.0", + "vite-tsconfig-paths": "^4.2.1", "vitest": "^0.31.1" }, "publishConfig": { diff --git a/src/MDXEditor.tsx b/src/MDXEditor.tsx index 433e2b9b..7ad8fd28 100644 --- a/src/MDXEditor.tsx +++ b/src/MDXEditor.tsx @@ -10,6 +10,7 @@ import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary.js' import classNames from 'classnames' import { ToMarkdownOptions } from './exportMarkdownFromLexical' import { noop } from './utils/fp' +import { IconKey } from './plugins/core/Icon' const LexicalProvider: React.FC<{ children: JSX.Element | string | (JSX.Element | string)[] }> = ({ children }) => { const [initialRootEditorState, nodes, readOnly] = corePluginHooks.useEmitterValues( @@ -118,12 +119,22 @@ export interface MDXEditorProps { * Note: Don't use this mode to render content for consumption - render the markdown using a library of your choice instead. */ readOnly?: boolean + /** + * Use this prop to customize the icons used across the editor. Pass a function that returns an icon (JSX) for a given icon key. + */ + iconComponentFor?: (name: IconKey) => JSX.Element } const DEFAULT_MARKDOWN_OPTIONS: ToMarkdownOptions = { listItemIndent: 'one' } +const DefaultIcon = React.lazy(() => import('./plugins/core/Icon')) + +const defaultIconComponentFor = (name: IconKey) => { + return +} + /** * The interface for the {@link MDXEditor} object reference. * @@ -233,7 +244,8 @@ export const MDXEditor = React.forwardRef((pro toMarkdownOptions: props.toMarkdownOptions ?? DEFAULT_MARKDOWN_OPTIONS, autoFocus: props.autoFocus ?? false, placeholder: props.placeholder ?? '', - readOnly: Boolean(props.readOnly) + readOnly: Boolean(props.readOnly), + iconComponentFor: props.iconComponentFor ?? defaultIconComponentFor }), ...(props.plugins || []) ]} diff --git a/src/icons/emergency_home.svg b/src/icons/admonition.svg similarity index 100% rename from src/icons/emergency_home.svg rename to src/icons/admonition.svg diff --git a/src/icons/arrow_drop_down.svg b/src/icons/arrow_drop_down.svg index 37a95ad4..7dfae360 100644 --- a/src/icons/arrow_drop_down.svg +++ b/src/icons/arrow_drop_down.svg @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + diff --git a/src/icons/arrow_right.svg b/src/icons/arrow_right.svg deleted file mode 100644 index 039a69f8..00000000 --- a/src/icons/arrow_right.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/icons/delete.svg b/src/icons/delete_big.svg similarity index 100% rename from src/icons/delete.svg rename to src/icons/delete_big.svg diff --git a/src/icons/deployed_code.svg b/src/icons/sandpack.svg similarity index 100% rename from src/icons/deployed_code.svg rename to src/icons/sandpack.svg diff --git a/src/plugins/core/Icon.tsx b/src/plugins/core/Icon.tsx new file mode 100644 index 00000000..72f90664 --- /dev/null +++ b/src/plugins/core/Icon.tsx @@ -0,0 +1,143 @@ +import React from 'react' + +import add_column from '@/icons/add_column.svg' +import add_photo from '@/icons/add_photo.svg' +import add_row from '@/icons/add_row.svg' +import arrow_drop_down from '@/icons/arrow_drop_down.svg' +import check from '@/icons/check.svg' +import check_small from '@/icons/check_small.svg' +import close from '@/icons/close.svg' +import code from '@/icons/code.svg' +import content_copy from '@/icons/content_copy.svg' +import delete_big from '@/icons/delete_big.svg' +import delete_small from '@/icons/delete_small.svg' +import sandpack from '@/icons/sandpack.svg' +import difference from '@/icons/difference.svg' +import edit from '@/icons/edit.svg' +import admonition from '@/icons/admonition.svg' +import extension from '@/icons/extension.svg' +import format_align_center from '@/icons/format_align_center.svg' +import format_align_left from '@/icons/format_align_left.svg' +import format_align_right from '@/icons/format_align_right.svg' +import format_bold from '@/icons/format_bold.svg' +import format_italic from '@/icons/format_italic.svg' +import format_list_bulleted from '@/icons/format_list_bulleted.svg' +import format_list_checked from '@/icons/format_list_checked.svg' +import format_list_numbered from '@/icons/format_list_numbered.svg' +import format_underlined from '@/icons/format_underlined.svg' +import frame_source from '@/icons/frame_source.svg' +import frontmatter from '@/icons/frontmatter.svg' +import horizontal_rule from '@/icons/horizontal_rule.svg' +import insert_col_left from '@/icons/insert_col_left.svg' +import insert_col_right from '@/icons/insert_col_right.svg' +import insert_row_above from '@/icons/insert_row_above.svg' +import insert_row_below from '@/icons/insert_row_below.svg' +import link from '@/icons/link.svg' +import link_off from '@/icons/link_off.svg' +import markdown from '@/icons/markdown.svg' +import more_horiz from '@/icons/more_horiz.svg' +import more_vert from '@/icons/more_vert.svg' +import open_in_new from '@/icons/open_in_new.svg' +import redo from '@/icons/redo.svg' +import rich_text from '@/icons/rich_text.svg' +import settings from '@/icons/settings.svg' +import table from '@/icons/table.svg' +import undo from '@/icons/undo.svg' + +export type IconKey = + | 'add_column' + | 'add_photo' + | 'add_row' + | 'arrow_drop_down' + | 'check' + | 'check_small' + | 'close' + | 'code' + | 'content_copy' + | 'delete_big' + | 'delete_small' + | 'sandpack' + | 'difference' + | 'edit' + | 'admonition' + | 'extension' + | 'format_align_center' + | 'format_align_left' + | 'format_align_right' + | 'format_bold' + | 'format_italic' + | 'format_list_bulleted' + | 'format_list_checked' + | 'format_list_numbered' + | 'format_underlined' + | 'frame_source' + | 'frontmatter' + | 'horizontal_rule' + | 'insert_col_left' + | 'insert_col_right' + | 'insert_row_above' + | 'insert_row_below' + | 'link' + | 'link_off' + | 'markdown' + | 'more_horiz' + | 'more_vert' + | 'open_in_new' + | 'redo' + | 'rich_text' + | 'settings' + | 'table' + | 'undo' + +const IconMap: Record = { + add_column, + add_photo, + add_row, + arrow_drop_down, + check, + check_small, + close, + code, + content_copy, + delete_big, + delete_small, + sandpack, + difference, + edit, + admonition, + extension, + format_align_center, + format_align_left, + format_align_right, + format_bold, + format_italic, + format_list_bulleted, + format_list_checked, + format_list_numbered, + format_underlined, + frame_source, + frontmatter, + horizontal_rule, + insert_col_left, + insert_col_right, + insert_row_above, + insert_row_below, + link, + link_off, + markdown, + more_horiz, + more_vert, + open_in_new, + redo, + rich_text, + settings, + table, + undo +} + +const Icon: React.FC<{ name: IconKey }> = ({ name }) => { + const IconComponent = IconMap[name] + return +} + +export default Icon diff --git a/src/plugins/core/PropertyPopover.tsx b/src/plugins/core/PropertyPopover.tsx index 25874146..56f1b8cf 100644 --- a/src/plugins/core/PropertyPopover.tsx +++ b/src/plugins/core/PropertyPopover.tsx @@ -2,10 +2,10 @@ import * as RadixPopover from '@radix-ui/react-popover' import React from 'react' import { useForm } from 'react-hook-form' -import SettingsIcon from '../../icons/settings.svg' import styles from '../../styles/ui.module.css' import { PopoverContent, PopoverPortal } from './ui/PopoverUtils' +import { corePluginHooks } from '.' /** * The properties of the {@link PropertyPopover} React component. @@ -31,13 +31,14 @@ export interface PropertyPopoverProps { */ export const PropertyPopover: React.FC = ({ title, properties, onChange }) => { const [open, setOpen] = React.useState(false) + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const { register, handleSubmit, reset } = useForm({ defaultValues: properties }) return ( setOpen(v)}> - +
{iconComponentFor('settings')}
diff --git a/src/plugins/core/index.ts b/src/plugins/core/index.ts index 7c8c8359..e30b4404 100644 --- a/src/plugins/core/index.ts +++ b/src/plugins/core/index.ts @@ -47,6 +47,7 @@ import { SharedHistoryPlugin } from './SharedHistoryPlugin' import { noop } from '../../utils/fp' import { controlOrMeta } from '../../utils/detectMac' import { MdastBreakVisitor } from './MdastBreakVisitor' +import { type IconKey } from './Icon' /** @internal */ export type EditorSubscription = (activeEditor: LexicalEditor) => () => void @@ -108,6 +109,10 @@ export const coreSystem = system((r) => { const onBlur = r.node() + const iconComponentFor = r.node<(name: IconKey) => React.ReactNode>((name: IconKey) => { + throw new Error(`No icon component for ${name}`) + }) + const rebind = () => r.o.scan((teardowns, [subs, activeEditorValue]: [EditorSubscription[], LexicalEditor]) => { teardowns.forEach((teardown) => { @@ -413,7 +418,7 @@ export const coreSystem = system((r) => { } else { $insertNodeToNearestRoot(node) } - if (Object.hasOwn(node, 'select') && typeof node.select === 'function') { + if ('select' in node && typeof node.select === 'function') { // eslint-disable-next-line @typescript-eslint/no-unsafe-call setTimeout(() => node.select()) } @@ -499,7 +504,9 @@ export const coreSystem = system((r) => { insertDecoratorNode, // Events - onBlur + onBlur, + + iconComponentFor } }, []) @@ -512,6 +519,7 @@ interface CorePluginParams { onBlur?: (e: FocusEvent) => void toMarkdownOptions: NonNullable readOnly: boolean + iconComponentFor: (name: IconKey) => React.ReactElement } export const [ @@ -537,6 +545,7 @@ export const [ init(realm, params: CorePluginParams) { realm.pubKey('initialMarkdown', params.initialMarkdown.trim()) + realm.pubKey('iconComponentFor', params.iconComponentFor) // core import visitors realm.pubKey('addImportVisitor', MdastRootVisitor) diff --git a/src/plugins/core/ui/DownshiftAutoComplete.tsx b/src/plugins/core/ui/DownshiftAutoComplete.tsx index 3e7b2897..7ea00b5a 100644 --- a/src/plugins/core/ui/DownshiftAutoComplete.tsx +++ b/src/plugins/core/ui/DownshiftAutoComplete.tsx @@ -2,7 +2,7 @@ import { useCombobox } from 'downshift' import React from 'react' import { Control, UseFormSetValue, Controller, UseFormRegister } from 'react-hook-form' import styles from '../../../styles/ui.module.css' -import DropDownIcon from '../../../icons/arrow_drop_down.svg' +import { corePluginHooks } from '..' const MAX_SUGGESTIONS = 20 @@ -35,6 +35,7 @@ export const DownshiftAutoCompleteWithSuggestions: React.FC { const [items, setItems] = React.useState(suggestions.slice(0, MAX_SUGGESTIONS)) + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const enableAutoComplete = suggestions.length > 0 @@ -85,7 +86,7 @@ export const DownshiftAutoCompleteWithSuggestions: React.FC {enableAutoComplete && ( )} diff --git a/src/plugins/frontmatter/FrontmatterEditor.tsx b/src/plugins/frontmatter/FrontmatterEditor.tsx index 69b067c6..0d5b3f47 100644 --- a/src/plugins/frontmatter/FrontmatterEditor.tsx +++ b/src/plugins/frontmatter/FrontmatterEditor.tsx @@ -4,8 +4,6 @@ import YamlParser from 'js-yaml' import React from 'react' import { useFieldArray, useForm } from 'react-hook-form' import { frontmatterPluginHooks } from '.' -import CloseIcon from '../../icons/close.svg' -import DeleteIcon from '../../icons/delete.svg' import styles from '../../styles/ui.module.css' import { corePluginHooks } from '../core' @@ -17,7 +15,11 @@ export interface FrontmatterEditorProps { } export const FrontmatterEditor = ({ yaml, onChange }: FrontmatterEditorProps) => { - const [readOnly, editorRootElementRef] = corePluginHooks.useEmitterValues('readOnly', 'editorRootElementRef') + const [readOnly, editorRootElementRef, iconComponentFor] = corePluginHooks.useEmitterValues( + 'readOnly', + 'editorRootElementRef', + 'iconComponentFor' + ) const [frontmatterDialogOpen] = frontmatterPluginHooks.useEmitterValues('frontmatterDialogOpen') const setFrontmatterDialogOpen = frontmatterPluginHooks.usePublisher('frontmatterDialogOpen') const removeFrontmatter = frontmatterPluginHooks.usePublisher('removeFrontmatter') @@ -100,7 +102,7 @@ export const FrontmatterEditor = ({ yaml, onChange }: FrontmatterEditorProps) => @@ -135,7 +137,7 @@ export const FrontmatterEditor = ({ yaml, onChange }: FrontmatterEditorProps) => diff --git a/src/plugins/image/ImageEditor.tsx b/src/plugins/image/ImageEditor.tsx index f2d37a67..0dba5555 100644 --- a/src/plugins/image/ImageEditor.tsx +++ b/src/plugins/image/ImageEditor.tsx @@ -21,8 +21,8 @@ import { SELECTION_CHANGE_COMMAND } from 'lexical' import { imagePluginHooks } from '.' -import SettingsIcon from '../../icons/settings.svg' import styles from '../../styles/ui.module.css' +import { corePluginHooks } from '../core' import { $isImageNode } from './ImageNode' import ImageResizer from './ImageResizer' @@ -94,6 +94,7 @@ export function ImageEditor({ src, title, alt, nodeKey, width, height }: ImageEd const [imagePreviewHandler] = imagePluginHooks.useEmitterValues('imagePreviewHandler') const [imageSource, setImageSource] = React.useState(null) const openEditImageDialog = imagePluginHooks.usePublisher('openEditImageDialog') + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const onDelete = React.useCallback( (payload: KeyboardEvent) => { @@ -273,7 +274,7 @@ export function ImageEditor({ src, title, alt, nodeKey, width, height }: ImageEd }) }} > - + {iconComponentFor('settings')} diff --git a/src/plugins/link-dialog/LinkDialog.tsx b/src/plugins/link-dialog/LinkDialog.tsx index 023f91ee..5e23b313 100644 --- a/src/plugins/link-dialog/LinkDialog.tsx +++ b/src/plugins/link-dialog/LinkDialog.tsx @@ -5,18 +5,13 @@ import * as Popover from '@radix-ui/react-popover' import * as Tooltip from '@radix-ui/react-tooltip' import React from 'react' -import { createCommand, LexicalCommand } from 'lexical' -import CheckIcon from '../../icons/check.svg' -import CopyIcon from '../../icons/content_copy.svg' -import EditIcon from '../../icons/edit.svg' -import LinkOffIcon from '../../icons/link_off.svg' -import OpenInNewIcon from '../../icons/open_in_new.svg' +import { corePluginHooks } from '@/plugins/core' +import { DownshiftAutoComplete } from '@/plugins/core/ui/DownshiftAutoComplete' +import styles from '@/styles/ui.module.css' import classNames from 'classnames' -import styles from '../../styles/ui.module.css' -import { corePluginHooks } from '../core' -import { linkDialogPluginHooks } from '.' +import { createCommand, LexicalCommand } from 'lexical' import { useForm } from 'react-hook-form' -import { DownshiftAutoComplete } from '../core/ui/DownshiftAutoComplete' +import { linkDialogPluginHooks } from '.' export const OPEN_LINK_DIALOG: LexicalCommand = createCommand() @@ -95,6 +90,7 @@ export const LinkDialog: React.FC = () => { const [editorRootElementRef] = corePluginHooks.useEmitterValues('editorRootElementRef') const publishWindowChange = linkDialogPluginHooks.usePublisher('onWindowChange') const [activeEditor] = corePluginHooks.useEmitterValues('activeEditor') + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const [linkDialogState, linkAutocompleteSuggestions] = linkDialogPluginHooks.useEmitterValues( 'linkDialogState', @@ -166,10 +162,10 @@ export const LinkDialog: React.FC = () => { title={urlIsExternal ? `Open ${linkDialogState.url} in new window` : linkDialogState.url} > {linkDialogState.url} - {urlIsExternal && } + {urlIsExternal && iconComponentFor('open_in_new')} switchFromPreviewToLinkEdit(true)} title="Edit link URL" aria-label="Edit link URL"> - + {iconComponentFor('edit')} @@ -184,7 +180,7 @@ export const LinkDialog: React.FC = () => { }) }} > - {copyUrlTooltipOpen ? : } + {copyUrlTooltipOpen ? iconComponentFor('check') : iconComponentFor('content_copy')} @@ -197,10 +193,11 @@ export const LinkDialog: React.FC = () => { removeLink(true)}> - + {iconComponentFor('link_off')} )} + diff --git a/src/plugins/table/TableEditor.tsx b/src/plugins/table/TableEditor.tsx index ce0ce233..66b715d5 100644 --- a/src/plugins/table/TableEditor.tsx +++ b/src/plugins/table/TableEditor.tsx @@ -17,30 +17,19 @@ import { } from 'lexical' import * as Mdast from 'mdast' import React from 'react' -import { lexicalTheme } from '../../styles/lexicalTheme' import { exportLexicalTreeToMdast } from '../../exportMarkdownFromLexical' import { importMdastTreeToLexical } from '../../importMarkdownToLexical' -import AddColumnIcon from '../../icons/add_column.svg' -import AddRowIcon from '../../icons/add_row.svg' -import DeleteSmallIcon from '../../icons/delete_small.svg' -import AlignCenterIcon from '../../icons/format_align_center.svg' -import AlignLeftIcon from '../../icons/format_align_left.svg' -import AlignRightIcon from '../../icons/format_align_right.svg' -import InsertColLeftIcon from '../../icons/insert_col_left.svg' -import InsertColRightIcon from '../../icons/insert_col_right.svg' -import InsertRowAboveIcon from '../../icons/insert_row_above.svg' -import InsertRowBelowIcon from '../../icons/insert_row_below.svg' -import MoreHorizIcon from '../../icons/more_horiz.svg' +import { lexicalTheme } from '../../styles/lexicalTheme' import { TableNode } from './TableNode' +import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin.js' +import { mergeRegister } from '@lexical/utils' import * as RadixToolbar from '@radix-ui/react-toolbar' import classNames from 'classnames' import styles from '../../styles/ui.module.css' -import { corePluginHooks } from '../core' -import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin.js' -import { mergeRegister } from '@lexical/utils' import { isPartOftheEditorUI } from '../../utils/isPartOftheEditorUI' import { uuidv4 } from '../../utils/uuid4' +import { corePluginHooks } from '../core' const AlignToTailwindClassMap = { center: styles.centeredCell, @@ -56,6 +45,7 @@ export interface TableEditorProps { export const TableEditor: React.FC = ({ mdastNode, parentEditor, lexicalTable }) => { const [activeCell, setActiveCell] = React.useState<[number, number] | null>(null) + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const getCellKey = React.useMemo(() => { const cellKeyMap = new WeakMap() return (cell: Mdast.TableCell) => { @@ -193,7 +183,7 @@ export const TableEditor: React.FC = ({ mdastNode, parentEdito }) }} > - + {iconComponentFor('delete_small')} {Array.from({ length: mdastNode.children[0].children.length }, (_, colIndex) => { @@ -243,7 +233,7 @@ export const TableEditor: React.FC = ({ mdastNode, parentEdito {rowIndex === 0 && ( )} @@ -256,7 +246,7 @@ export const TableEditor: React.FC = ({ mdastNode, parentEdito @@ -422,6 +412,7 @@ const ColumnEditor: React.FC = ({ setActiveCellWithBoundaries }) => { const [editorRootElementRef] = corePluginHooks.useEmitterValues('editorRootElementRef') + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const insertColumnAt = React.useCallback( (colIndex: number) => { @@ -457,7 +448,7 @@ const ColumnEditor: React.FC = ({ data-active={highlightedCoordinates[0] === colIndex + 1} title="Column menu" > - + {iconComponentFor('more_horiz')} = ({ aria-label="Text alignment" > - + {iconComponentFor('format_align_left')} - + {iconComponentFor('format_align_center')} - + {iconComponentFor('format_align_right')} - + {iconComponentFor('insert_col_left')} - + {iconComponentFor('insert_col_right')} - + {iconComponentFor('delete_small')} + @@ -518,6 +510,7 @@ const RowEditor: React.FC = ({ setActiveCellWithBoundaries }) => { const [editorRootElementRef] = corePluginHooks.useEmitterValues('editorRootElementRef') + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const insertRowAt = React.useCallback( (rowIndex: number) => { @@ -541,7 +534,7 @@ const RowEditor: React.FC = ({ return ( - + {iconComponentFor('more_horiz')} = ({ > - + {iconComponentFor('insert_row_above')} - + {iconComponentFor('insert_row_below')} - + {iconComponentFor('delete_small')} + diff --git a/src/plugins/toolbar/components/BoldItalicUnderlineToggles.tsx b/src/plugins/toolbar/components/BoldItalicUnderlineToggles.tsx index bd5b8ddd..411ffcdc 100644 --- a/src/plugins/toolbar/components/BoldItalicUnderlineToggles.tsx +++ b/src/plugins/toolbar/components/BoldItalicUnderlineToggles.tsx @@ -2,9 +2,6 @@ import React from 'react' import { corePluginHooks } from '../../core' import { IS_BOLD, IS_ITALIC, IS_UNDERLINE } from '../../../FormatConstants' import { MultipleChoiceToggleGroup } from '.././primitives/toolbar' -import BoldIcon from '../../../icons/format_bold.svg' -import ItalicIcon from '../../../icons/format_italic.svg' -import UnderlinedIcon from '../../../icons/format_underlined.svg' /** * A toolbar component that lets the user toggle bold, italic and underline formatting. @@ -20,15 +17,16 @@ export const BoldItalicUnderlineToggles: React.FC = () => { const boldTitle = boldIsOn ? 'Remove bold' : 'Bold' const italicTitle = italicIsOn ? 'Remove italic' : 'Italic' const underlineTitle = underlineIsOn ? 'Remove underline' : 'Underline' + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') return ( , active: boldIsOn, onChange: applyFormat.bind(null, 'bold') }, - { title: italicTitle, contents: , active: italicIsOn, onChange: applyFormat.bind(null, 'italic') }, + { title: boldTitle, contents: iconComponentFor('format_bold'), active: boldIsOn, onChange: applyFormat.bind(null, 'bold') }, + { title: italicTitle, contents: iconComponentFor('format_italic'), active: italicIsOn, onChange: applyFormat.bind(null, 'italic') }, { title: underlineTitle, - contents: , + contents:
{iconComponentFor('format_underlined')}
, active: underlineIsOn, onChange: applyFormat.bind(null, 'underline') } diff --git a/src/plugins/toolbar/components/CodeToggle.tsx b/src/plugins/toolbar/components/CodeToggle.tsx index 3249f77d..2bea1660 100644 --- a/src/plugins/toolbar/components/CodeToggle.tsx +++ b/src/plugins/toolbar/components/CodeToggle.tsx @@ -1,6 +1,5 @@ import React from 'react' import { IS_CODE } from '../../../FormatConstants' -import CodeIcon from '../../../icons/code.svg' import { corePluginHooks } from '../../core' import { MultipleChoiceToggleGroup } from '.././primitives/toolbar' @@ -9,7 +8,7 @@ import { MultipleChoiceToggleGroup } from '.././primitives/toolbar' * Use for inline `code` elements (like variables, methods, etc). */ export const CodeToggle: React.FC = () => { - const [currentFormat] = corePluginHooks.useEmitterValues('currentFormat') + const [currentFormat, iconComponentFor] = corePluginHooks.useEmitterValues('currentFormat', 'iconComponentFor') const applyFormat = corePluginHooks.usePublisher('applyFormat') const codeIsOn = (currentFormat & IS_CODE) !== 0 @@ -18,7 +17,7 @@ export const CodeToggle: React.FC = () => { return ( , active: codeIsOn, onChange: applyFormat.bind(null, 'code') }]} + items={[{ title: title, contents: iconComponentFor('code'), active: codeIsOn, onChange: applyFormat.bind(null, 'code') }]} /> ) } diff --git a/src/plugins/toolbar/components/CreateLink.tsx b/src/plugins/toolbar/components/CreateLink.tsx index cf2ce025..c835f0d4 100644 --- a/src/plugins/toolbar/components/CreateLink.tsx +++ b/src/plugins/toolbar/components/CreateLink.tsx @@ -1,6 +1,5 @@ import React from 'react' import { ButtonWithTooltip } from '.././primitives/toolbar' -import LinkIcon from '../../../icons/link.svg' import { linkDialogPluginHooks } from '../../link-dialog' /** @@ -9,6 +8,7 @@ import { linkDialogPluginHooks } from '../../link-dialog' */ export const CreateLink = () => { const openLinkDialog = linkDialogPluginHooks.usePublisher('openLinkEditDialog') + const [iconComponentFor] = linkDialogPluginHooks.useEmitterValues('iconComponentFor') return ( { openLinkDialog(true) }} > - + {iconComponentFor('link')} ) } diff --git a/src/plugins/toolbar/components/DiffSourceToggleWrapper.tsx b/src/plugins/toolbar/components/DiffSourceToggleWrapper.tsx index 859b7fb8..a52e2b5a 100644 --- a/src/plugins/toolbar/components/DiffSourceToggleWrapper.tsx +++ b/src/plugins/toolbar/components/DiffSourceToggleWrapper.tsx @@ -1,9 +1,6 @@ import React from 'react' import { diffSourcePluginHooks } from '../../diff-source' import { SingleChoiceToggleGroup } from '.././primitives/toolbar' -import RichTextIcon from '../../../icons/rich_text.svg' -import DiffIcon from '../../../icons/difference.svg' -import SourceIcon from '../../../icons/markdown.svg' import styles from '../../../styles/ui.module.css' /** @@ -23,6 +20,7 @@ import styles from '../../../styles/ui.module.css' export const DiffSourceToggleWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [viewMode] = diffSourcePluginHooks.useEmitterValues('viewMode') const changeViewMode = diffSourcePluginHooks.usePublisher('viewMode') + const [iconComponentFor] = diffSourcePluginHooks.useEmitterValues('iconComponentFor') return ( <> @@ -39,9 +37,9 @@ export const DiffSourceToggleWrapper: React.FC<{ children: React.ReactNode }> = className={styles.diffSourceToggle} value={viewMode} items={[ - { title: 'Rich text', contents: , value: 'rich-text' }, - { title: 'Diff mode', contents: , value: 'diff' }, - { title: 'Source', contents: , value: 'source' } + { title: 'Rich text', contents: iconComponentFor('rich_text'), value: 'rich-text' }, + { title: 'Diff mode', contents: iconComponentFor('difference'), value: 'diff' }, + { title: 'Source', contents: iconComponentFor('markdown'), value: 'source' } ]} onChange={(value) => changeViewMode(value || 'rich-text')} /> diff --git a/src/plugins/toolbar/components/InsertAdmonition.tsx b/src/plugins/toolbar/components/InsertAdmonition.tsx index 64d561b6..a27ef6e9 100644 --- a/src/plugins/toolbar/components/InsertAdmonition.tsx +++ b/src/plugins/toolbar/components/InsertAdmonition.tsx @@ -1,6 +1,5 @@ import React from 'react' import { ButtonOrDropdownButton } from '.././primitives/toolbar' -import AdmonitionIcon from '../../../icons/emergency_home.svg' import { directivesPluginHooks } from '../../directives' import { ADMONITION_TYPES } from '../../../directive-editors/AdmonitionDirectiveDescriptor' @@ -10,6 +9,7 @@ import { ADMONITION_TYPES } from '../../../directive-editors/AdmonitionDirective */ export const InsertAdmonition = () => { const insertDirective = directivesPluginHooks.usePublisher('insertDirective') + const [iconComponentFor] = directivesPluginHooks.useEmitterValues('iconComponentFor') const items = React.useMemo( () => ADMONITION_TYPES.map((type) => ({ value: type, label: type.replace(/^./, (l) => l.toUpperCase()) })), [] @@ -26,7 +26,7 @@ export const InsertAdmonition = () => { }} items={items} > - + {iconComponentFor('admonition')} ) } diff --git a/src/plugins/toolbar/components/InsertCodeBlock.tsx b/src/plugins/toolbar/components/InsertCodeBlock.tsx index f737e7ce..d4758b06 100644 --- a/src/plugins/toolbar/components/InsertCodeBlock.tsx +++ b/src/plugins/toolbar/components/InsertCodeBlock.tsx @@ -1,6 +1,5 @@ import React from 'react' import { ButtonWithTooltip } from '.././primitives/toolbar' -import FrameSourceIcon from '../../../icons/frame_source.svg' import { codeBlockPluginHooks } from '../../codeblock/' /** @@ -10,6 +9,7 @@ import { codeBlockPluginHooks } from '../../codeblock/' */ export const InsertCodeBlock: React.FC = () => { const insertCodeBlock = codeBlockPluginHooks.usePublisher('insertCodeBlock') + const [iconComponentFor] = codeBlockPluginHooks.useEmitterValues('iconComponentFor') return ( { insertCodeBlock({}) }} > - + {iconComponentFor('frame_source')} ) } diff --git a/src/plugins/toolbar/components/InsertFrontmatter.tsx b/src/plugins/toolbar/components/InsertFrontmatter.tsx index 31220190..aa806b7a 100644 --- a/src/plugins/toolbar/components/InsertFrontmatter.tsx +++ b/src/plugins/toolbar/components/InsertFrontmatter.tsx @@ -1,6 +1,5 @@ import React from 'react' import { ButtonWithTooltip } from '.././primitives/toolbar' -import FrontmatterIcon from '../../../icons/frontmatter.svg' import { frontmatterPluginHooks } from '../../frontmatter' import styles from '../../../styles/ui.module.css' import classNames from 'classnames' @@ -12,6 +11,7 @@ import classNames from 'classnames' export const InsertFrontmatter: React.FC = () => { const insertFrontmatter = frontmatterPluginHooks.usePublisher('insertFrontmatter') const [hasFrontmatter] = frontmatterPluginHooks.useEmitterValues('hasFrontmatter') + const [iconComponentFor] = frontmatterPluginHooks.useEmitterValues('iconComponentFor') return ( { })} onClick={insertFrontmatter.bind(null, true)} > - + {iconComponentFor('frontmatter')} ) } diff --git a/src/plugins/toolbar/components/InsertImage.tsx b/src/plugins/toolbar/components/InsertImage.tsx index ee7314cc..11f64b5b 100644 --- a/src/plugins/toolbar/components/InsertImage.tsx +++ b/src/plugins/toolbar/components/InsertImage.tsx @@ -1,7 +1,6 @@ import React from 'react' import { imagePluginHooks } from '../../image' import * as RadixToolbar from '@radix-ui/react-toolbar' -import AddPhotoIcon from '../../../icons/add_photo.svg' import styles from '../../../styles/ui.module.css' import { corePluginHooks } from '../../core/index' import { TooltipWrap } from '../primitives/TooltipWrap' @@ -12,13 +11,11 @@ import { TooltipWrap } from '../primitives/TooltipWrap' */ export const InsertImage = React.forwardRef>((_, forwardedRef) => { const openNewImageDialog = imagePluginHooks.usePublisher('openNewImageDialog') - const [readOnly] = corePluginHooks.useEmitterValues('readOnly') + const [readOnly, iconComponentFor] = corePluginHooks.useEmitterValues('readOnly', 'iconComponentFor') return ( openNewImageDialog(true)}> - - - + {iconComponentFor('add_photo')} ) }) diff --git a/src/plugins/toolbar/components/InsertSandpack.tsx b/src/plugins/toolbar/components/InsertSandpack.tsx index 3c7f4130..7d8b3ca2 100644 --- a/src/plugins/toolbar/components/InsertSandpack.tsx +++ b/src/plugins/toolbar/components/InsertSandpack.tsx @@ -1,6 +1,5 @@ import React from 'react' import { ButtonOrDropdownButton } from '.././primitives/toolbar' -import LiveCodeIcon from '../../../icons/deployed_code.svg' import { sandpackPluginHooks } from '../../sandpack' /** @@ -11,10 +10,11 @@ export const InsertSandpack = () => { const [sandpackConfig] = sandpackPluginHooks.useEmitterValues('sandpackConfig') const insertSandpack = sandpackPluginHooks.usePublisher('insertSandpack') const items = React.useMemo(() => sandpackConfig.presets.map((preset) => ({ value: preset.name, label: preset.label })), [sandpackConfig]) + const [iconComponentFor] = sandpackPluginHooks.useEmitterValues('iconComponentFor') return ( - + {iconComponentFor('sandpack')} ) } diff --git a/src/plugins/toolbar/components/InsertTable.tsx b/src/plugins/toolbar/components/InsertTable.tsx index 7aa29638..c4498c1d 100644 --- a/src/plugins/toolbar/components/InsertTable.tsx +++ b/src/plugins/toolbar/components/InsertTable.tsx @@ -1,13 +1,13 @@ import { ButtonWithTooltip } from '.././primitives/toolbar' import React from 'react' import { tablePluginHooks } from '../../table' -import TableIcon from '../../../icons/table.svg' /** * A toolbar button that allows the user to insert a table. * For this button to work, you need to have the `tablePlugin` plugin enabled. */ export const InsertTable: React.FC = () => { + const [iconComponentFor] = tablePluginHooks.useEmitterValues('iconComponentFor') const insertTable = tablePluginHooks.usePublisher('insertTable') return ( @@ -17,7 +17,7 @@ export const InsertTable: React.FC = () => { insertTable({ rows: 3, columns: 3 }) }} > - + {iconComponentFor('table')} ) } diff --git a/src/plugins/toolbar/components/InsertThematicBreak.tsx b/src/plugins/toolbar/components/InsertThematicBreak.tsx index dea25c03..bf4d8ad9 100644 --- a/src/plugins/toolbar/components/InsertThematicBreak.tsx +++ b/src/plugins/toolbar/components/InsertThematicBreak.tsx @@ -1,7 +1,6 @@ import React from 'react' import { thematicBreakPluginHooks } from '../../thematic-break' import { ButtonWithTooltip } from '.././primitives/toolbar' -import HorizontalRuleIcon from '../../../icons/horizontal_rule.svg' /** * A toolbar button that allows the user to insert a thematic break (rendered as an HR HTML element). @@ -9,9 +8,10 @@ import HorizontalRuleIcon from '../../../icons/horizontal_rule.svg' */ export const InsertThematicBreak: React.FC = () => { const insertThematicBreak = thematicBreakPluginHooks.usePublisher('insertThematicBreak') + const [iconComponentFor] = thematicBreakPluginHooks.useEmitterValues('iconComponentFor') return ( - + {iconComponentFor('horizontal_rule')} ) } diff --git a/src/plugins/toolbar/components/ListsToggle.tsx b/src/plugins/toolbar/components/ListsToggle.tsx index 815cf569..3004f22c 100644 --- a/src/plugins/toolbar/components/ListsToggle.tsx +++ b/src/plugins/toolbar/components/ListsToggle.tsx @@ -1,7 +1,4 @@ import React from 'react' -import BulletedListIcon from '../../../icons/format_list_bulleted.svg' -import NumberedListIcon from '../../../icons/format_list_numbered.svg' -import CheckedListIcon from '../../../icons/format_list_checked.svg' import { listsPluginHooks } from '../../lists' import { SingleChoiceToggleGroup } from '.././primitives/toolbar' @@ -13,13 +10,14 @@ import { SingleChoiceToggleGroup } from '.././primitives/toolbar' export const ListsToggle: React.FC = () => { const [currentListType] = listsPluginHooks.useEmitterValues('currentListType') const applyListType = listsPluginHooks.usePublisher('applyListType') + const [iconComponentFor] = listsPluginHooks.useEmitterValues('iconComponentFor') return ( , value: 'bullet' }, - { title: 'Numbered list', contents: , value: 'number' }, - { title: 'Check list', contents: , value: 'check' } + { title: 'Bulleted list', contents: iconComponentFor('format_list_bulleted'), value: 'bullet' }, + { title: 'Numbered list', contents: iconComponentFor('format_list_numbered'), value: 'number' }, + { title: 'Check list', contents: iconComponentFor('format_list_checked'), value: 'check' } ]} onChange={applyListType} /> diff --git a/src/plugins/toolbar/components/ShowSandpackInfo.tsx b/src/plugins/toolbar/components/ShowSandpackInfo.tsx index a3a9563f..e943ef7f 100644 --- a/src/plugins/toolbar/components/ShowSandpackInfo.tsx +++ b/src/plugins/toolbar/components/ShowSandpackInfo.tsx @@ -1,10 +1,9 @@ import React from 'react' -import { corePluginHooks } from '../../core' -import { CodeBlockNode } from '../../codeblock/CodeBlockNode' import styles from '../../../styles/ui.module.css' +import { CodeBlockNode } from '../../codeblock/CodeBlockNode' +import { corePluginHooks } from '../../core' import { sandpackPluginHooks } from '../../sandpack' import { ButtonWithTooltip } from '.././primitives/toolbar' -import DeleteIcon from '../../../icons/delete.svg' /** * A component that displays the focused live code block's name. @@ -12,7 +11,7 @@ import DeleteIcon from '../../../icons/delete.svg' * See {@link ConditionalContents} for an example on how to display the dropdown only when a sandpack editor is in focus. */ export const ShowSandpackInfo = () => { - const [editorInFocus, theEditor] = corePluginHooks.useEmitterValues('editorInFocus', 'activeEditor') + const [editorInFocus, theEditor, iconComponentFor] = corePluginHooks.useEmitterValues('editorInFocus', 'activeEditor', 'iconComponentFor') const sandpackNode = editorInFocus!.rootNode as CodeBlockNode const [sandpackConfig] = sandpackPluginHooks.useEmitterValues('sandpackConfig') @@ -33,7 +32,7 @@ export const ShowSandpackInfo = () => { }) }} > - + {iconComponentFor('delete_big')} diff --git a/src/plugins/toolbar/components/UndoRedo.tsx b/src/plugins/toolbar/components/UndoRedo.tsx index a0763bd3..9d5d90e0 100644 --- a/src/plugins/toolbar/components/UndoRedo.tsx +++ b/src/plugins/toolbar/components/UndoRedo.tsx @@ -1,8 +1,6 @@ import { mergeRegister } from '@lexical/utils' import { CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND } from 'lexical' import React from 'react' -import RedoIcon from '../../../icons/redo.svg' -import UndoIcon from '../../../icons/undo.svg' import { IS_APPLE } from '../../../utils/detectMac' import { corePluginHooks } from '../../core' import { MultipleChoiceToggleGroup } from '.././primitives/toolbar' @@ -11,6 +9,7 @@ import { MultipleChoiceToggleGroup } from '.././primitives/toolbar' * A toolbar component that lets the user undo and redo changes in the editor. */ export const UndoRedo: React.FC = () => { + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const [activeEditor] = corePluginHooks.useEmitterValues('activeEditor') const [canUndo, setCanUndo] = React.useState(false) const [canRedo, setCanRedo] = React.useState(false) @@ -44,14 +43,14 @@ export const UndoRedo: React.FC = () => { { title: IS_APPLE ? 'Undo (⌘Z)' : 'Undo (Ctrl+Z)', disabled: !canUndo, - contents: , + contents: iconComponentFor('undo'), active: false, onChange: () => activeEditor?.dispatchCommand(UNDO_COMMAND, undefined) }, { title: IS_APPLE ? 'Redo (⌘Y)' : 'Redo (Ctrl+Y)', disabled: !canRedo, - contents: , + contents: iconComponentFor('redo'), active: false, onChange: () => activeEditor?.dispatchCommand(REDO_COMMAND, undefined) } diff --git a/src/plugins/toolbar/primitives/DialogButton.tsx b/src/plugins/toolbar/primitives/DialogButton.tsx index c3a2f91d..70df1697 100644 --- a/src/plugins/toolbar/primitives/DialogButton.tsx +++ b/src/plugins/toolbar/primitives/DialogButton.tsx @@ -5,9 +5,6 @@ import React from 'react' import classNames from 'classnames' import { useCombobox } from 'downshift' -import DropDownIcon from '../../../icons/arrow_drop_down.svg' -import CheckIcon from '../../../icons/check.svg' -import CloseIcon from '../../../icons/close.svg' import { corePluginHooks } from '../../core' import styles from '../../../styles/ui.module.css' import { TooltipWrap } from './TooltipWrap' @@ -96,6 +93,7 @@ const DialogForm: React.FC<{ onSubmitCallback: (value: string) => void }> = ({ autocompleteSuggestions, onSubmitCallback, dialogInputPlaceholder, submitButtonTitle }) => { const [items, setItems] = React.useState(autocompleteSuggestions.slice(0, MAX_SUGGESTIONS)) + const [iconComponentFor] = corePluginHooks.useEmitterValues('iconComponentFor') const enableAutoComplete = autocompleteSuggestions.length > 0 @@ -166,7 +164,7 @@ const DialogForm: React.FC<{ /> {enableAutoComplete && ( )} @@ -193,12 +191,10 @@ const DialogForm: React.FC<{ aria-label={submitButtonTitle} className={classNames(styles.actionButton, styles.primaryActionButton)} > - + {iconComponentFor('check')} - - - + {iconComponentFor('close')} ) } diff --git a/src/plugins/toolbar/primitives/TooltipWrap.tsx b/src/plugins/toolbar/primitives/TooltipWrap.tsx index 5c9b1f41..21804ca7 100644 --- a/src/plugins/toolbar/primitives/TooltipWrap.tsx +++ b/src/plugins/toolbar/primitives/TooltipWrap.tsx @@ -14,7 +14,7 @@ export const TooltipWrap = React.forwardRef - {children} + {children} diff --git a/src/plugins/toolbar/primitives/select.tsx b/src/plugins/toolbar/primitives/select.tsx index 6c2bcfed..d3bffd76 100644 --- a/src/plugins/toolbar/primitives/select.tsx +++ b/src/plugins/toolbar/primitives/select.tsx @@ -1,6 +1,5 @@ import React from 'react' import * as RadixSelect from '@radix-ui/react-select' -import DropDownIcon from '../../../icons/arrow_drop_down.svg' import classNames from 'classnames' import styles from '../../../styles/ui.module.css' import { TooltipWrap } from './TooltipWrap' @@ -23,7 +22,7 @@ export const SelectItem = React.forwardRef = ({ title, placeholder, className }) => { - const [readOnly] = corePluginHooks.useEmitterValues('readOnly') + const [readOnly, iconComponentFor] = corePluginHooks.useEmitterValues('readOnly', 'iconComponentFor') return ( - - - + {iconComponentFor('arrow_drop_down')} ) @@ -67,14 +64,12 @@ export const SelectButtonTrigger: React.FC<{ children: React.ReactNode; title: s title, className }) => { - const [readOnly] = corePluginHooks.useEmitterValues('readOnly') + const [readOnly, iconComponentFor] = corePluginHooks.useEmitterValues('readOnly', 'iconComponentFor') return ( {children} - - - + {iconComponentFor('arrow_drop_down')} ) diff --git a/src/styles/ui.module.css b/src/styles/ui.module.css index 4657f044..6ec15d76 100644 --- a/src/styles/ui.module.css +++ b/src/styles/ui.module.css @@ -14,7 +14,7 @@ @define-mixin icon-button { @mixin clear-form-element; - padding: var(--spacing-2); + padding: var(--spacing-1_5); & svg { display: block; @@ -29,7 +29,8 @@ } &[data-state=on], &:active { - background-color: var(--accentBgActive); + background-color: var(--baseBgHover); + color: var(--baseTextContrast); } } @@ -47,6 +48,7 @@ --accentText: var(--blue11); --accentTextContrast: var(--blue12); + --basePageBg: white; --baseBase: var(--slate1); --baseBgSubtle: var(--slate2); --baseBg: var(--slate3); @@ -145,16 +147,16 @@ flex-direction: row; gap: var(--spacing-1); border-radius: var(--radius-medium); - padding: var(--spacing-2) var(--spacing-2); + padding: var(--spacing-1_5); align-items: center; overflow-x: auto; position: sticky; top: 0; - background-color: var(--baseBgSubtle); + background-color: var(--baseBg); width: inherit; & div[role=separator] { - margin: var(--spacing-2) var(--spacing-2); + margin: var(--spacing-2) var(--spacing-1); border-left: 1px solid var(--baseBorder); border-right: 1px solid var(--baseBase); height: var(--spacing-4); @@ -217,7 +219,7 @@ } &:hover { - background-color: var(--baseBgHover); + background-color: var(--baseBgActive); } &:active svg { @@ -225,7 +227,8 @@ } &[data-state=on], &:active { - background-color: var(--accentBgActive); + color: var(--baseTextContrast); + background-color: var(--baseBgActive); } &[data-disabled] { @@ -251,16 +254,21 @@ .toolbarCodeBlockLanguageSelectContent, .selectContainer { + @mixin drop-shadow-sm; z-index: 3; width: var(--spacing-36); border-bottom-left-radius: var(--radius-base); border-bottom-right-radius: var(--radius-base); - background-color: var(--baseBgHover); + background-color: var(--basePageBg); font-size: var(--text-sm); } .toolbarButtonDropdownContainer { border-top-right-radius: var(--radius-base); + + & .selectItem:first-child { + border-top-right-radius: var(--radius-base); + } } .toolbarNodeKindSelectTrigger, @@ -272,24 +280,21 @@ color: inherit; align-items: center; width: var(--spacing-36); - padding: var(--spacing-1); - padding-inline-start: var(--spacing-4) ; - padding-inline-end: var(--spacing-2); + padding: var(--spacing-1) var(--spacing-2); border-radius: var(--radius-medium); white-space: nowrap; flex-wrap: nowrap; font-size: var(--text-sm); - - &:hover { - background-color: var(--baseBgHover); - } + background-color: var(--basePageBg); + margin: 0 var(--spacing-1); &[data-state=open] { + @mixin drop-shadow-sm; border-bottom-right-radius: var(--radius-none); border-bottom-left-radius: var(--radius-none); - background-color: var(--baseBgHover); } } + .selectTrigger[data-placeholder] > span:first-child { color: var(--baseBorderHover); } @@ -299,6 +304,7 @@ width: auto; padding-inline-start: var(--spacing-2) ; padding-inline-end: var(--spacing-1); + padding-block: var(--spacing-0_5); } .toolbarCodeBlockLanguageSelectTrigger, @@ -312,14 +318,15 @@ { cursor: default; display: flex; - padding: var(--spacing-2) var(--spacing-4); + padding: var(--spacing-2); &[data-highlighted] { - background-color: var(--baseBgSubtle); + background-color: var(--baseBg); } &[data-state=checked] { - background-color: var(--accentBgActive); + color: var(--baseTextContrast); + background-color: var(--baseBg); } &[data-highlighted] { @@ -335,6 +342,8 @@ .toolbarNodeKindSelectDropdownArrow, .selectDropdownArrow { margin-left: auto; + display: flex; + align-items: center; } .contentEditable { @@ -445,7 +454,7 @@ border: 1px solid var(--accentBorder); background-color: var(--accentSolidHover); color:var(--baseBase); - font-size: var(--text-sm); + font-size: var(--text-xs); border-radius: var(--radius-medium); &:disabled { background: var(--accentLine); @@ -535,9 +544,9 @@ align-items: center; gap: var(--spacing-0_5); border-radius:var(--radius-medium); - border: 1px solid var(--baseBorder); - background-color: var(--baseBgSubtle); - padding:var(--spacing-2) var(--spacing-2); + border: 1px solid var(--baseBg); + background-color: var(--basePageBg); + padding:var(--spacing-1) var(--spacing-2); font-size: var(--text-sm); } @@ -577,11 +586,9 @@ } .popoverArrow { - fill: var(--baseBgSubtle); + fill: var(--basePageBg); } - - .linkDialogPreviewAnchor { margin-right: var(--spacing-3); display: flex; @@ -603,6 +610,9 @@ white-space: nowrap; } } +.tooltipTrigger { + align-self: center; +} .tooltipContent { z-index: 2; @@ -645,7 +655,7 @@ } & > tbody > tr > td:not(.toolCell) { - border: 1px solid var(--baseBorder); + border: 1px solid var(--baseBgActive); padding: var(--spacing-1) var(--spacing-2); white-space: normal; @@ -657,7 +667,7 @@ } &[data-active=true] { - outline: solid 2px var(--accentSolid) + outline: solid 1px var(--baseSolid); } } @@ -700,6 +710,7 @@ & > button { @mixin icon-button; } + & [role=separator] { margin-left: var(--spacing-1); margin-right: var(--spacing-1); @@ -753,18 +764,21 @@ display: flex; align-items: center; - border-radius: var(--radius-medium); } .addRowButton { width: 100%; - margin-top: var(--spacing-2); + margin-top: var(--spacing-px); box-sizing: border-box; + border-bottom-right-radius: var(--radius-medium); + border-bottom-left-radius: var(--radius-medium); } .addColumnButton { - margin-left: var(--spacing-2); + margin-left: var(--spacing-px); height: 100%; + border-top-right-radius: var(--radius-medium); + border-bottom-right-radius: var(--radius-medium); } /** Dialog */ @@ -881,7 +895,6 @@ } .diffSourceToggle { - border: 1px solid var(--baseBorder); border-radius: var(--radius-medium); display: flex; .toolbarToggleItem { @@ -889,7 +902,7 @@ & > span { display: block; - padding: var(--spacing-2) var(--spacing-3); + padding: var(--spacing-1) var(--spacing-2); } } } @@ -1086,13 +1099,17 @@ form.multiFieldForm { display: flex; flex-direction: column; - padding: var(--spacing-4); - gap: var(--spacing-4); + padding: var(--spacing-2); + gap: var(--spacing-2); .formField { display: flex; flex-direction: column; gap: var(--spacing-2); + + & label { + font-size: var(--text-xs); + } } } diff --git a/tsconfig.json b/tsconfig.json index dbec42c1..72e4c9a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,11 @@ "@testing-library/jest-dom", "svgr" ], + "paths": { + "@/*": [ + "./src/*" + ] + }, "plugins": [ { "name": "typescript-plugin-css-modules" diff --git a/vite.config.ts b/vite.config.ts index 18cfe3dd..873a46bd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs' import react from '@vitejs/plugin-react' import dts from 'vite-plugin-dts' import svgr from 'vite-plugin-svgr' -import fs from 'fs' +import tsconfigPaths from 'vite-tsconfig-paths' const ext = { cjs: 'cjs', @@ -42,6 +42,7 @@ export default defineConfig({ replaceAttrValues: { 'black': 'currentColor' } } }), + tsconfigPaths() ], server: { proxy: {