Browse Source

fix(admin-ui): Fix styling of React RichTextEditor component

Fixes #2885
Michael Bromley 1 năm trước cách đây
mục cha
commit
353ae50658

+ 0 - 431
packages/admin-ui/src/lib/core/src/shared/components/rich-text-editor/prosemirror/prosemirror.scss

@@ -1,431 +0,0 @@
-::ng-deep {
-    .ProseMirror {
-        position: relative;
-    }
-
-    .ProseMirror {
-        word-wrap: break-word;
-        white-space: pre-wrap;
-        -webkit-font-variant-ligatures: none;
-        font-variant-ligatures: none;
-    }
-
-    .ProseMirror pre {
-        white-space: pre-wrap;
-    }
-
-    .ProseMirror li {
-        position: relative;
-    }
-
-    .ProseMirror-hideselection *::selection {
-        background: transparent;
-    }
-
-    .ProseMirror-hideselection *::-moz-selection {
-        background: transparent;
-    }
-
-    .ProseMirror-hideselection {
-        caret-color: transparent;
-    }
-
-    .ProseMirror-selectednode {
-        outline: 2px solid var(--color-primary-500);
-    }
-
-    /* Make sure li selections wrap around markers */
-
-    li.ProseMirror-selectednode {
-        outline: none;
-    }
-
-    li.ProseMirror-selectednode:after {
-        content: '';
-        position: absolute;
-        left: -32px;
-        right: -2px;
-        top: -2px;
-        bottom: -2px;
-        border: 2px solid var(--color-primary-500);
-        pointer-events: none;
-    }
-
-    .ProseMirror-textblock-dropdown {
-        min-width: 3em;
-    }
-
-    .ProseMirror-menu {
-        margin: 0 -4px;
-        line-height: 1;
-    }
-
-    .ProseMirror-tooltip .ProseMirror-menu {
-        width: -webkit-fit-content;
-        width: fit-content;
-        white-space: pre;
-    }
-
-    .ProseMirror-menuitem {
-        margin-inline-end: 3px;
-        display: inline-block;
-    }
-
-    .ProseMirror-menuseparator {
-        border-inline-end: 1px solid var(--color-component-border-200);
-        margin: 0 12px 0 8px;
-        height: 18px;
-    }
-
-    .ProseMirror-menu-dropdown,
-    .ProseMirror-menu-dropdown-menu {
-        font-size: 90%;
-        white-space: nowrap;
-        border-radius: var(--border-radius-input);
-    }
-
-    .ProseMirror-menu-dropdown {
-        vertical-align: 1px;
-        cursor: pointer;
-        position: relative;
-        padding-inline-end: 15px;
-    }
-
-    .ProseMirror-menu-dropdown-wrap {
-        padding: 1px 3px 1px 6px;
-        display: inline-block;
-        position: relative;
-    }
-
-    .ProseMirror-menu-dropdown:after {
-        content: '';
-        border-inline-start: 4px solid transparent;
-        border-inline-end: 4px solid transparent;
-        border-top: 4px solid currentColor;
-        opacity: 0.6;
-        position: absolute;
-        right: 4px;
-        top: calc(50% - 2px);
-    }
-
-    .ProseMirror-menu-dropdown-menu,
-    .ProseMirror-menu-submenu {
-        position: absolute;
-        background: var(--color-component-bg-100);
-        border: 1px solid var(--color-component-border-200);
-        padding: 2px;
-    }
-
-    .ProseMirror-menu-dropdown-menu {
-        z-index: 15;
-        min-width: 6em;
-        color: var(--color-text-200);
-    }
-
-    .ProseMirror-menu-dropdown-item {
-        cursor: pointer;
-        padding: 2px 8px 2px 4px;
-    }
-
-    .ProseMirror-menu-dropdown-item:hover {
-        background: var(--color-component-bg-200);
-    }
-
-    .ProseMirror-menu-submenu-wrap {
-        position: relative;
-        margin-inline-end: 4px;
-    }
-
-    .ProseMirror-menu-submenu-label:after {
-        content: '';
-        border-top: 4px solid transparent;
-        border-bottom: 4px solid transparent;
-        border-inline-start: 4px solid currentColor;
-        opacity: 0.6;
-        position: absolute;
-        right: -8px;
-        top: calc(50% - 4px);
-    }
-
-    .ProseMirror-menu-submenu {
-        display: none;
-        min-width: 4em;
-        left: 100%;
-        top: -3px;
-    }
-
-    .ProseMirror-menu-active {
-        background: var(--color-component-bg-100);
-        border-radius: 4px;
-    }
-
-    .ProseMirror-menu-active {
-        background: var(--color-component-bg-100);
-        border-radius: 4px;
-    }
-
-    .ProseMirror-menu-disabled {
-        opacity: 0.3;
-    }
-
-    .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu,
-    .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
-        display: block;
-    }
-
-    .ProseMirror-menubar {
-        border-top-left-radius: inherit;
-        border-top-right-radius: inherit;
-        position: relative;
-        min-height: 1em;
-        color: var(--color-grey-600);
-        padding: 1px 6px;
-        top: 0;
-        left: 0;
-        right: 0;
-        background: var(--color-component-bg-100);
-        z-index: 10;
-        -moz-box-sizing: border-box;
-        box-sizing: border-box;
-        overflow: visible;
-        align-items: center;
-    }
-
-    .ProseMirror-icon {
-        display: inline-block;
-        line-height: 0.8;
-        vertical-align: -2px; /* Compensate for padding */
-        padding: 2px 8px;
-        cursor: pointer;
-    }
-
-    .ProseMirror-menu-disabled.ProseMirror-icon {
-        cursor: default;
-    }
-
-    .ProseMirror-icon svg {
-        fill: currentColor;
-        height: 1em;
-    }
-
-    .ProseMirror-icon span {
-        vertical-align: text-top;
-    }
-
-    .ProseMirror-gapcursor {
-        display: none;
-        pointer-events: none;
-        position: absolute;
-    }
-
-    .ProseMirror-gapcursor:after {
-        content: '';
-        display: block;
-        position: absolute;
-        top: -2px;
-        width: 20px;
-        border-top: 1px solid black;
-        animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
-    }
-
-    @keyframes ProseMirror-cursor-blink {
-        to {
-            visibility: hidden;
-        }
-    }
-
-    .ProseMirror-focused .ProseMirror-gapcursor {
-        display: block;
-    }
-
-    .ProseMirror ul,
-    .ProseMirror ol {
-        padding-inline-start: 30px;
-        list-style-position: initial;
-    }
-
-    .ProseMirror blockquote {
-        padding-inline-start: 1em;
-        border-inline-start: 3px solid var(--color-grey-100);
-        margin-inline-start: 0;
-        margin-inline-end: 0;
-    }
-
-    .ProseMirror-prompt {
-        background: white;
-        padding: 5px 10px 5px 15px;
-        border: 1px solid silver;
-        position: fixed;
-        border-radius: 3px;
-        z-index: 11;
-        box-shadow: -0.5px 2px 5px rgba(0, 0, 0, 0.2);
-    }
-
-    .ProseMirror-prompt h5 {
-        margin: 0;
-        font-weight: normal;
-        font-size: 100%;
-        color: var(--color-grey-500);
-    }
-
-    .ProseMirror-prompt input[type='text'],
-    .ProseMirror-prompt textarea {
-        background: var(--color-component-bg-100);
-        border: none;
-        outline: none;
-    }
-
-    .ProseMirror-prompt input[type='text'] {
-        padding: 0 4px;
-    }
-
-    .ProseMirror-prompt-close {
-        position: absolute;
-        left: 2px;
-        top: 1px;
-        color: var(--color-grey-400);
-        border: none;
-        background: transparent;
-        padding: 0;
-    }
-
-    .ProseMirror-prompt-close:after {
-        content: '✕';
-        font-size: 12px;
-    }
-
-    .ProseMirror-invalid {
-        background: var(--color-warning-200);
-        border: 1px solid var(--color-warning-300);
-        border-radius: 4px;
-        padding: 5px 10px;
-        position: absolute;
-        min-width: 10em;
-    }
-
-    .ProseMirror-prompt-buttons {
-        margin-top: 5px;
-        display: none;
-    }
-
-    #editor,
-    .editor {
-        background: var(--color-form-input-bg);
-        color: black;
-        background-clip: padding-box;
-        border-radius: 4px;
-        border: 2px solid rgba(0, 0, 0, 0.2);
-        padding: 5px 0;
-        margin-bottom: 23px;
-    }
-
-    .ProseMirror p:first-child,
-    .ProseMirror h1:first-child,
-    .ProseMirror h2:first-child,
-    .ProseMirror h3:first-child,
-    .ProseMirror h4:first-child,
-    .ProseMirror h5:first-child,
-    .ProseMirror h6:first-child {
-        margin-top: 10px;
-    }
-
-    .ProseMirror {
-        padding: 4px 8px 4px 14px;
-        line-height: 1.2;
-        outline: none;
-    }
-
-    .ProseMirror p {
-        margin-bottom: 0.5rem;
-        color: var(--color-text-100) !important;
-    }
-
-    .ProseMirror .tableWrapper {
-        td,
-        th {
-            border: 1px solid var(--color-grey-300);
-            padding: 3px 6px;
-        }
-        td p,
-        th p {
-            margin-top: 0;
-        }
-        th,
-        th p {
-            font-weight: bold;
-        }
-    }
-
-    .ProseMirror table {
-      border-collapse: collapse;
-      table-layout: fixed;
-      width: 100%;
-      overflow: hidden;
-    }
-    .ProseMirror td,
-    .ProseMirror th {
-      vertical-align: top;
-      box-sizing: border-box;
-      position: relative;
-    }
-    .ProseMirror .column-resize-handle {
-      position: absolute;
-      right: -2px;
-      top: 0;
-      bottom: 0;
-      width: 4px;
-      z-index: 20;
-      background-color: #adf;
-      pointer-events: none;
-    }
-    .ProseMirror.resize-cursor {
-      cursor: ew-resize;
-      cursor: col-resize;
-    }
-    /* Give selected cells a blue overlay */
-    .ProseMirror .selectedCell:after {
-      z-index: 2;
-      position: absolute;
-      content: '';
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      background: #afdaf355;
-      pointer-events: none;
-    }
-
-
-
-    .menu-separator {
-        border-bottom: 1px solid var(--color-grey-400);
-        height: 0;
-        margin: 6px 0;
-        pointer-events: none;
-    }
-    .menu-item-with-icon {
-        display: flex;
-        align-items: center;
-        clr-icon,
-        .custom-icon {
-            margin-inline-end: 4px;
-            color: var(--color-text-200);
-        }
-        .hr-icon {
-            width: 13px;
-            height: 8px;
-            border-bottom: 2px solid var(--color-text-100);
-            margin: -8px 5px 0 2px;
-        }
-        .h-icon {
-            width: 16px;
-            text-align: center;
-            font-weight: bold;
-            font-size: 12px;
-        }
-    }
-}
-
-.context-menu {
-    position: fixed;
-}

+ 7 - 71
packages/admin-ui/src/lib/core/src/shared/components/rich-text-editor/rich-text-editor.component.scss

@@ -1,5 +1,9 @@
 @import 'variables';
-@import 'prosemirror/prosemirror';
+
+// Note: the custom styling for the Prosemirror editor is located
+// in /src/lib/static/styles/component/prosemirror.scss, because it is
+// shared also by the corresponding React component. Therefore it is not
+// scoped to this Angular component.
 
 :host {
     display: block;
@@ -12,74 +16,6 @@
     }
 }
 
-label.rich-text-label {
-    font-size: var(--font-size-sm);
-    color: var(--font-weight-700);
-}
-
-::ng-deep .ProseMirror-menubar {
-    position: sticky;
-    border: 1px solid var(--color-weight-200);
-    border-bottom: none;
-    background-color: var(--color-component-bg-200);
-    color: var(--color-icon-button);
-    border-radius: var(--border-radius-input) var(--border-radius-input) 0 0;
-    padding: 6px 12px;
-    display: flex;
-    flex-wrap: wrap;
-}
-
-::ng-deep .vdr-prosemirror {
-    background: var(--color-form-input-bg);
-    color: var(--color-text-100);
-    min-height: 128px;
-    max-height: 600px;
-    min-width: 200px;
-    border: 1px solid var(--color-weight-200);
-    border-radius: 0 0 var(--border-radius-input) var(--border-radius-input);
-    transition: border-color 0.2s;
-    overflow: auto;
-    text-align: initial;
-
-    &:focus {
-        border-color: var(--color-primary-500) !important;
-        box-shadow: 0 0 1px 1px var(--color-primary-100);
-    }
-
-    /* Add space around the hr to make clicking it easier */
-
-    hr {
-        padding: 2px 10px;
-        border: none;
-        margin: 1em 0;
-    }
-
-    hr:after {
-        content: '';
-        display: block;
-        height: 1px;
-        background-color: silver;
-        line-height: 2px;
-    }
-
-    img {
-        cursor: default;
-        max-width: 100%;
-    }
-
-    a:link,
-    a:visited {
-        color: var(--color-primary-700);
-        text-decoration: none;
-    }
-
-    .iframe-wrapper {
-        width: 100%;
-        text-align: center;
-        padding: 6px;
-        transition: background-color 0.3s;
-        &:hover {
-            background-color: var(--color-primary-100);
-        }
-    }
+.context-menu {
+    position: fixed;
 }

+ 9 - 3
packages/admin-ui/src/lib/react/src/react-components/RichTextEditor.tsx

@@ -18,6 +18,7 @@ export type RichTextEditorType = InputHTMLAttributes<HTMLInputElement> & {
      * Copied from real property description.
      */
     attributes?: Record<string, string>;
+    label?: string;
     readOnly?: boolean;
     onMount?: (editor: ProsemirrorService) => void;
 };
@@ -58,7 +59,7 @@ export type RichTextEditorType = InputHTMLAttributes<HTMLInputElement> & {
  */
 export const RichTextEditor = forwardRef((props: RichTextEditorType, ref: ForwardedRef<HTMLInputElement>) => {
     const [data, setData] = useState<string>('');
-    const { readOnly, ...rest } = props;
+    const { readOnly, label, ...rest } = props;
     const { ref: _ref, editor } = useRichTextEditor({
         attributes: props.attributes,
         isReadOnly: () => readOnly || false,
@@ -79,12 +80,17 @@ export const RichTextEditor = forwardRef((props: RichTextEditorType, ref: Forwar
             }
         },
     });
+
     useEffect(() => {
-        if (props.onMount && editor) props.onMount(editor);
+        if (props.onMount && editor) {
+            props.onMount(editor);
+        }
     }, []);
     return (
         <>
-            <div ref={_ref} {...rest} />
+            <div ref={_ref} {...rest}>
+                {label && <label className="rich-text-label">{label}</label>}
+            </div>
             <input type="hidden" value={data} ref={ref} />
         </>
     );

+ 511 - 0
packages/admin-ui/src/lib/static/styles/component/prosemirror.scss

@@ -0,0 +1,511 @@
+label.rich-text-label {
+    font-size: var(--font-size-sm);
+    color: var(--font-weight-700);
+}
+
+.ProseMirror-menubar {
+    position: sticky;
+    border: 1px solid var(--color-weight-200);
+    border-bottom: none;
+    background-color: var(--color-component-bg-200);
+    color: var(--color-icon-button);
+    border-radius: var(--border-radius-input) var(--border-radius-input) 0 0;
+    padding: 6px 12px;
+    display: flex;
+    flex-wrap: wrap;
+}
+
+.vdr-prosemirror {
+    background: var(--color-form-input-bg);
+    color: var(--color-text-100);
+    min-height: 128px;
+    max-height: 600px;
+    min-width: 200px;
+    border: 1px solid var(--color-weight-200);
+    border-radius: 0 0 var(--border-radius-input) var(--border-radius-input);
+    transition: border-color 0.2s;
+    overflow: auto;
+    text-align: initial;
+
+    &:focus {
+        border-color: var(--color-primary-500) !important;
+        box-shadow: 0 0 1px 1px var(--color-primary-100);
+    }
+
+    /* Add space around the hr to make clicking it easier */
+
+    hr {
+        padding: 2px 10px;
+        border: none;
+        margin: 1em 0;
+    }
+
+    hr:after {
+        content: '';
+        display: block;
+        height: 1px;
+        background-color: silver;
+        line-height: 2px;
+    }
+
+    img {
+        cursor: default;
+        max-width: 100%;
+    }
+
+    a:link,
+    a:visited {
+        color: var(--color-primary-700);
+        text-decoration: none;
+    }
+
+    .iframe-wrapper {
+        width: 100%;
+        text-align: center;
+        padding: 6px;
+        transition: background-color 0.3s;
+        &:hover {
+            background-color: var(--color-primary-100);
+        }
+    }
+}
+
+
+.ProseMirror {
+    position: relative;
+}
+
+.ProseMirror {
+    word-wrap: break-word;
+    white-space: pre-wrap;
+    -webkit-font-variant-ligatures: none;
+    font-variant-ligatures: none;
+}
+
+.ProseMirror pre {
+    white-space: pre-wrap;
+}
+
+.ProseMirror li {
+    position: relative;
+}
+
+.ProseMirror-hideselection *::selection {
+    background: transparent;
+}
+
+.ProseMirror-hideselection *::-moz-selection {
+    background: transparent;
+}
+
+.ProseMirror-hideselection {
+    caret-color: transparent;
+}
+
+.ProseMirror-selectednode {
+    outline: 2px solid var(--color-primary-500);
+}
+
+/* Make sure li selections wrap around markers */
+
+li.ProseMirror-selectednode {
+    outline: none;
+}
+
+li.ProseMirror-selectednode:after {
+    content: '';
+    position: absolute;
+    left: -32px;
+    right: -2px;
+    top: -2px;
+    bottom: -2px;
+    border: 2px solid var(--color-primary-500);
+    pointer-events: none;
+}
+
+.ProseMirror-textblock-dropdown {
+    min-width: 3em;
+}
+
+.ProseMirror-menu {
+    margin: 0 -4px;
+    line-height: 1;
+}
+
+.ProseMirror-tooltip .ProseMirror-menu {
+    width: -webkit-fit-content;
+    width: fit-content;
+    white-space: pre;
+}
+
+.ProseMirror-menuitem {
+    margin-inline-end: 3px;
+    display: inline-block;
+}
+
+.ProseMirror-menuseparator {
+    border-inline-end: 1px solid var(--color-component-border-200);
+    margin: 0 12px 0 8px;
+    height: 18px;
+}
+
+.ProseMirror-menu-dropdown,
+.ProseMirror-menu-dropdown-menu {
+    font-size: 90%;
+    white-space: nowrap;
+    border-radius: var(--border-radius-input);
+}
+
+.ProseMirror-menu-dropdown {
+    vertical-align: 1px;
+    cursor: pointer;
+    position: relative;
+    padding-inline-end: 15px;
+}
+
+.ProseMirror-menu-dropdown-wrap {
+    padding: 1px 3px 1px 6px;
+    display: inline-block;
+    position: relative;
+}
+
+.ProseMirror-menu-dropdown:after {
+    content: '';
+    border-inline-start: 4px solid transparent;
+    border-inline-end: 4px solid transparent;
+    border-top: 4px solid currentColor;
+    opacity: 0.6;
+    position: absolute;
+    right: 4px;
+    top: calc(50% - 2px);
+}
+
+.ProseMirror-menu-dropdown-menu,
+.ProseMirror-menu-submenu {
+    position: absolute;
+    background: var(--color-component-bg-100);
+    border: 1px solid var(--color-component-border-200);
+    padding: 2px;
+}
+
+.ProseMirror-menu-dropdown-menu {
+    z-index: 15;
+    min-width: 6em;
+    color: var(--color-text-200);
+}
+
+.ProseMirror-menu-dropdown-item {
+    cursor: pointer;
+    padding: 2px 8px 2px 4px;
+}
+
+.ProseMirror-menu-dropdown-item:hover {
+    background: var(--color-component-bg-200);
+}
+
+.ProseMirror-menu-submenu-wrap {
+    position: relative;
+    margin-inline-end: 4px;
+}
+
+.ProseMirror-menu-submenu-label:after {
+    content: '';
+    border-top: 4px solid transparent;
+    border-bottom: 4px solid transparent;
+    border-inline-start: 4px solid currentColor;
+    opacity: 0.6;
+    position: absolute;
+    right: -8px;
+    top: calc(50% - 4px);
+}
+
+.ProseMirror-menu-submenu {
+    display: none;
+    min-width: 4em;
+    left: 100%;
+    top: -3px;
+}
+
+.ProseMirror-menu-active {
+    background: var(--color-component-bg-100);
+    border-radius: 4px;
+}
+
+.ProseMirror-menu-active {
+    background: var(--color-component-bg-100);
+    border-radius: 4px;
+}
+
+.ProseMirror-menu-disabled {
+    opacity: 0.3;
+}
+
+.ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu,
+.ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
+    display: block;
+}
+
+.ProseMirror-menubar {
+    border-top-left-radius: inherit;
+    border-top-right-radius: inherit;
+    position: relative;
+    min-height: 1em;
+    color: var(--color-grey-600);
+    padding: 1px 6px;
+    top: 0;
+    left: 0;
+    right: 0;
+    background: var(--color-component-bg-100);
+    z-index: 10;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    overflow: visible;
+    align-items: center;
+}
+
+.ProseMirror-icon {
+    display: inline-block;
+    line-height: 0.8;
+    vertical-align: -2px; /* Compensate for padding */
+    padding: 2px 8px;
+    cursor: pointer;
+}
+
+.ProseMirror-menu-disabled.ProseMirror-icon {
+    cursor: default;
+}
+
+.ProseMirror-icon svg {
+    fill: currentColor;
+    height: 1em;
+}
+
+.ProseMirror-icon span {
+    vertical-align: text-top;
+}
+
+.ProseMirror-gapcursor {
+    display: none;
+    pointer-events: none;
+    position: absolute;
+}
+
+.ProseMirror-gapcursor:after {
+    content: '';
+    display: block;
+    position: absolute;
+    top: -2px;
+    width: 20px;
+    border-top: 1px solid black;
+    animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
+}
+
+@keyframes ProseMirror-cursor-blink {
+    to {
+        visibility: hidden;
+    }
+}
+
+.ProseMirror-focused .ProseMirror-gapcursor {
+    display: block;
+}
+
+.ProseMirror ul,
+.ProseMirror ol {
+    padding-inline-start: 30px;
+    list-style-position: initial;
+}
+
+.ProseMirror blockquote {
+    padding-inline-start: 1em;
+    border-inline-start: 3px solid var(--color-grey-100);
+    margin-inline-start: 0;
+    margin-inline-end: 0;
+}
+
+.ProseMirror-prompt {
+    background: white;
+    padding: 5px 10px 5px 15px;
+    border: 1px solid silver;
+    position: fixed;
+    border-radius: 3px;
+    z-index: 11;
+    box-shadow: -0.5px 2px 5px rgba(0, 0, 0, 0.2);
+}
+
+.ProseMirror-prompt h5 {
+    margin: 0;
+    font-weight: normal;
+    font-size: 100%;
+    color: var(--color-grey-500);
+}
+
+.ProseMirror-prompt input[type='text'],
+.ProseMirror-prompt textarea {
+    background: var(--color-component-bg-100);
+    border: none;
+    outline: none;
+}
+
+.ProseMirror-prompt input[type='text'] {
+    padding: 0 4px;
+}
+
+.ProseMirror-prompt-close {
+    position: absolute;
+    left: 2px;
+    top: 1px;
+    color: var(--color-grey-400);
+    border: none;
+    background: transparent;
+    padding: 0;
+}
+
+.ProseMirror-prompt-close:after {
+    content: '✕';
+    font-size: 12px;
+}
+
+.ProseMirror-invalid {
+    background: var(--color-warning-200);
+    border: 1px solid var(--color-warning-300);
+    border-radius: 4px;
+    padding: 5px 10px;
+    position: absolute;
+    min-width: 10em;
+}
+
+.ProseMirror-prompt-buttons {
+    margin-top: 5px;
+    display: none;
+}
+
+#editor,
+.editor {
+    background: var(--color-form-input-bg);
+    color: black;
+    background-clip: padding-box;
+    border-radius: 4px;
+    border: 2px solid rgba(0, 0, 0, 0.2);
+    padding: 5px 0;
+    margin-bottom: 23px;
+}
+
+.ProseMirror p:first-child,
+.ProseMirror h1:first-child,
+.ProseMirror h2:first-child,
+.ProseMirror h3:first-child,
+.ProseMirror h4:first-child,
+.ProseMirror h5:first-child,
+.ProseMirror h6:first-child {
+    margin-top: 10px;
+}
+
+.ProseMirror {
+    padding: 4px 8px 4px 14px;
+    line-height: 1.2;
+    outline: none;
+}
+
+.ProseMirror p {
+    margin-bottom: 0.5rem;
+    color: var(--color-text-100) !important;
+}
+
+.ProseMirror .tableWrapper {
+    td,
+    th {
+        border: 1px solid var(--color-grey-300);
+        padding: 3px 6px;
+    }
+
+    td p,
+    th p {
+        margin-top: 0;
+    }
+
+    th,
+    th p {
+        font-weight: bold;
+    }
+}
+
+.ProseMirror table {
+    border-collapse: collapse;
+    table-layout: fixed;
+    width: 100%;
+    overflow: hidden;
+}
+
+.ProseMirror td,
+.ProseMirror th {
+    vertical-align: top;
+    box-sizing: border-box;
+    position: relative;
+}
+
+.ProseMirror .column-resize-handle {
+    position: absolute;
+    right: -2px;
+    top: 0;
+    bottom: 0;
+    width: 4px;
+    z-index: 20;
+    background-color: #adf;
+    pointer-events: none;
+}
+
+.ProseMirror.resize-cursor {
+    cursor: ew-resize;
+    cursor: col-resize;
+}
+
+/* Give selected cells a blue overlay */
+.ProseMirror .selectedCell:after {
+    z-index: 2;
+    position: absolute;
+    content: '';
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    background: #afdaf355;
+    pointer-events: none;
+}
+
+
+.menu-separator {
+    border-bottom: 1px solid var(--color-grey-400);
+    height: 0;
+    margin: 6px 0;
+    pointer-events: none;
+}
+
+.menu-item-with-icon {
+    display: flex;
+    align-items: center;
+
+    clr-icon,
+    .custom-icon {
+        margin-inline-end: 4px;
+        color: var(--color-text-200);
+    }
+
+    .hr-icon {
+        width: 13px;
+        height: 8px;
+        border-bottom: 2px solid var(--color-text-100);
+        margin: -8px 5px 0 2px;
+    }
+
+    .h-icon {
+        width: 16px;
+        text-align: center;
+        font-weight: bold;
+        font-size: 12px;
+    }
+}
+
+.context-menu {
+    position: fixed;
+}

+ 2 - 0
packages/admin-ui/src/lib/static/styles/styles.scss

@@ -11,6 +11,8 @@
 @import "fonts";
 @import "global/global";
 
+@import "component/prosemirror";
+
 @import 'theme/default';
 @import 'theme/dark';
 

+ 4 - 0
packages/dev-server/example-plugins/ui-extensions-library/ui/angular-components/angular-ui/angular-ui.component.html

@@ -37,6 +37,10 @@
                 <vdr-form-field label="Invalid with error">
                     <input type="text" [formControl]="invalidFormControl" />
                 </vdr-form-field>
+                <vdr-rich-text-editor
+                    class="form-grid-span"
+                    label="Description"
+                ></vdr-rich-text-editor>
             </div>
         </vdr-card>
 

+ 3 - 0
packages/dev-server/example-plugins/ui-extensions-library/ui/react-components/ReactUi.tsx

@@ -11,6 +11,7 @@ import {
     useInjector,
     usePageMetadata,
 } from '@vendure/admin-ui/react';
+import { RichTextEditor } from '@vendure/admin-ui/react';
 import React, { PropsWithChildren, useState } from 'react';
 
 export function ReactUi() {
@@ -75,6 +76,8 @@ export function ReactUi() {
                             <FormField label="Invalid with error" invalid={true}>
                                 <input type="text" />
                             </FormField>
+
+                            <RichTextEditor className="form-grid-span" label="Description" readOnly={false} />
                         </div>
                     </Card>