Skip to content

Commit

Permalink
Merge pull request #818 from GeorgeBatt-SV/MOS-1546-editor-toolbar-ad…
Browse files Browse the repository at this point in the history
…just

MOS-1546
  • Loading branch information
GeorgeBatt-SV authored Jan 21, 2025
2 parents 8968c60 + e7c13a3 commit 9b61615
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 125 deletions.
6 changes: 6 additions & 0 deletions containers/mosaic/src/components/Field/FieldTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ export interface FieldDefBase<Type, T = any> {
* Instructions about how to fill the current field.
*/
instructionText?: string;
/**
* If instruction text is provided, force it to be displayed
* in the form of a tooltip rather than alongside the field,
* even when the horizontal space is available.
*/
forceInstructionTooltip?: boolean;
/**
* Indicates whether the field can be written on or readonly.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EditorContent } from "@tiptap/react";
import TextareaAutosize from "@mui/material/TextareaAutosize";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import styled, { css } from "styled-components";

import theme from "@root/theme";
Expand Down Expand Up @@ -214,54 +215,67 @@ export const StyledFloatingToolbar = styled.div<{ $disabled?: boolean }>`
background: white;
box-shadow: box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
border: 1px solid var(--border-color);
padding: 4px 0;
border-bottom: 0;
`;

export const PrimaryToolbar = styled.div<{ $focus?: boolean }>`
export const StyledPrimaryToolbar = styled.div<{ $focus?: boolean }>`
background: ${theme.newColors.grey1["100"]};
border: 1px solid var(--border-color);
border-bottom: 0;
position: sticky;
padding: 4px 0;
top: -25px;
z-index: 1;
&::after {
border-bottom: 1px solid var(--border-color);
bottom: -1px;
content: " ";
left: 16px;
right: 16px;
position: absolute;
}
${({ $focus }) => $focus && `
border-color: ${theme.newColors.almostBlack["100"]};
`}
`;

export const ControlGroups = styled.div`
export const ToolbarOverflow = styled.div`
overflow: hidden;
`;

export const ToolbarOffset = styled.div`
margin-left: -1px;
`;

export const ControlRow = styled.div`
display: flex;
flex-wrap: wrap;
position: relative;
padding: 4px 0;
&::after {
border-bottom: 1px solid var(--border-color);
bottom: 0;
content: " ";
left: 8px;
right: 8px;
position: absolute;
}
&:last-child::after {
left: 0;
right: 0;
}
`;

export const ControlGroup = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding-left: 8px;
padding-right: 8px;
position: relative;
margin-left: 1px;
gap: 2px;
&::after {
&::before {
content: " ";
position: absolute;
border-right: 1px solid var(--border-color);
border-left: 1px solid var(--border-color);
top: 6px;
bottom: 6px;
right: -1px;
left: -1px;
}
`;

Expand Down Expand Up @@ -292,8 +306,18 @@ export const StyledControlButton = styled.button.attrs<{ $active?: boolean; $squ
`;

export const StyledTextStyleMenuButton = styled(StyledControlButton)`
width: 100px;
width: 110px;
text-align: center;
justify-content: start;
padding: 0;
padding-left: 4px;
`;

export const MenuButtonArrow = styled(KeyboardArrowDownIcon)`
margin-left: auto;
margin-right: -0.2em;
height: 20px !important;
width: 20px !important;
`;

export const MultipleStyles = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Skeleton from "@mui/material/Skeleton";
import type { SelectionType, FloatingToolbarState, EditorMode, NodeFormState, TextEditorInputSettings, TextEditorData } from "./FormFieldTextEditorTypes";
import type { MosaicFieldProps } from "../FieldTypes";

import { Editor, CodeView, StyledTextEditor, PrimaryToolbar } from "./FormFieldTextEditor.styled";
import { Editor, CodeView, StyledTextEditor } from "./FormFieldTextEditor.styled";
import { NodeForm } from "./NodeForm/NodeForm";
import { ToolbarControls, ModeSwitch } from "./Toolbar";
import { transformScriptTags } from "./Extensions/Script";
Expand All @@ -19,6 +19,7 @@ import { getDefaultExtensions } from "./Extensions/defaultExtensions";
import { escapeHtml } from "@root/utils/dom/escapeHtml";
import testIds from "@root/utils/testIds";
import { defaultControls, floatingControls, selectionVirtualElement } from "./textEditorUtils";
import PrimaryToolbar from "./Toolbar/PrimaryToolbar";

function FormFieldTextEditorUnmemo({
value = "",
Expand Down Expand Up @@ -193,7 +194,7 @@ function FormFieldTextEditorUnmemo({
focus={focus}
/>
{mode === "visual" && (
<PrimaryToolbar $focus={focus} data-testid={testIds.TEXT_EDITOR_PRIMARY_TOOLBAR}>
<PrimaryToolbar focus={focus}>
<ToolbarControls
editor={editor}
controls={controls}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export type TextEditorOnImageParams = Partial<TextEditorUpdateImageValues> & {
};

export interface TextEditorInputSettings {
controls?: ControlsConfig;
controls?: ControlsConfig[];
extensions?: Extensions;
onLink?: (params: TextEditorOnLinkParams) => void;
onImage?: (params: TextEditorOnImageParams) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function ControlMenuDropdown({
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);

const onClick = (e: MouseEvent<HTMLButtonElement>) => {
setAnchorEl(e.target as HTMLElement);
setAnchorEl(e.currentTarget);
};

const onClose = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from "react";

import type { MenuButtonProps } from "../../FormFieldTextEditorTypes";

import { MultipleStyles, StyledTextStyleMenuButton } from "../../FormFieldTextEditor.styled";
import { MenuButtonArrow, MultipleStyles, StyledTextStyleMenuButton } from "../../FormFieldTextEditor.styled";
import testIds from "@root/utils/testIds";

export function TextStyleMenuButton({ disabled, editor, onClick }: MenuButtonProps): ReactElement {
Expand All @@ -27,6 +27,7 @@ export function TextStyleMenuButton({ disabled, editor, onClick }: MenuButtonPro
data-testid={testIds.TEXT_EDITOR_HEADING_MENU}
>
{currentStyle || <MultipleStyles>Multiple Styles</MultipleStyles>}
<MenuButtonArrow />
</StyledTextStyleMenuButton>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { PropsWithChildren, ReactElement } from "react";

import React from "react";

import testIds from "@root/utils/testIds";
import { StyledPrimaryToolbar } from "../FormFieldTextEditor.styled";

function PrimaryToolbar({ children, focus }: PropsWithChildren<{focus?: boolean}>): ReactElement {
return (
<StyledPrimaryToolbar $focus={focus} data-testid={testIds.TEXT_EDITOR_PRIMARY_TOOLBAR}>
{children}
</StyledPrimaryToolbar>
);
}

export default PrimaryToolbar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { ReactElement } from "react";

import React, { useMemo } from "react";

import type { ControlsConfig } from "../FormFieldTextEditorTypes";
import type { ToolbarControlsProps } from "./ToolbarControls";

import { ControlButton, ControlMenuDropdown, resolveControls } from "./Controls";
import { ControlGroup, ControlRow } from "../FormFieldTextEditor.styled";
import testIds from "@root/utils/testIds";

type ToolbarControlRowProps = Omit<ToolbarControlsProps, "controls"> & {
controls: ControlsConfig;
}

export function ToolbarControlRow({
editor,
controls: controlsDef,
selectionTypes,
inputSettings = {},
disabled,
}: ToolbarControlRowProps): ReactElement {
const groups = useMemo(() => resolveControls(controlsDef, selectionTypes), [controlsDef, selectionTypes]);

return (
<ControlRow>
{groups.map((group, groupIndex) => (
<ControlGroup key={groupIndex}>
{group.map((control, index) => Array.isArray(control) ? (
<ControlMenuDropdown
key={index}
editor={editor}
controls={control}
inputSettings={inputSettings}
testId={`${testIds.TEXT_EDITOR_CONTROL}:menu-${groupIndex}-${index}`}
disabled={disabled}
/>
) : "MenuButton" in control ? (
<ControlMenuDropdown
key={index}
editor={editor}
controls={control.controls}
MenuButton={control.MenuButton}
inputSettings={inputSettings}
disabled={disabled}
/>
) : "Component" in control ? (
<control.Component
{...control}
key={index}
editor={editor}
inputSettings={inputSettings}
data-testid={`${testIds.TEXT_EDITOR_CONTROL}:${control.name}`}
disabled={disabled}
/>
) : (
<ControlButton
key={control.name}
onClick={(event) => control.cmd({ editor, inputSettings, event })}
label={control.label}
shortcut={control.shortcut}
active={editor.isActive(control.name)}
square
data-testid={`${testIds.TEXT_EDITOR_CONTROL}:${control.name}`}
disabled={disabled}
>
<control.Icon />
</ControlButton>
))}
</ControlGroup>
))}
</ControlRow>
);
}
Original file line number Diff line number Diff line change
@@ -1,78 +1,36 @@
import type { ReactElement } from "react";
import type { Editor } from "@tiptap/core";

import React, { useMemo } from "react";
import React from "react";

import type { ControlsConfig, SelectionType, TextEditorInputSettings } from "../FormFieldTextEditorTypes";

import { ControlButton, ControlMenuDropdown, resolveControls } from "./Controls";
import { ControlGroup, ControlGroups } from "../FormFieldTextEditor.styled";
import testIds from "@root/utils/testIds";
import { ToolbarControlRow } from "./ToolbarControlRow";
import { ToolbarOffset, ToolbarOverflow } from "../FormFieldTextEditor.styled";

export interface ToolbarControlsProps {
controls: ControlsConfig;
controls: ControlsConfig[];
editor: Editor;
selectionTypes?: SelectionType[];
inputSettings: TextEditorInputSettings;
disabled?: boolean;
}

export function ToolbarControls({
editor,
controls: controlsDef,
selectionTypes,
inputSettings = {},
disabled,
controls: controlRows,
...props
}: ToolbarControlsProps): ReactElement {
const groups = useMemo(() => resolveControls(controlsDef, selectionTypes), [controlsDef, selectionTypes]);

return (
<ControlGroups>
{groups.map((group, groupIndex) => (
<ControlGroup key={groupIndex}>
{group.map((control, index) => Array.isArray(control) ? (
<ControlMenuDropdown
key={index}
editor={editor}
controls={control}
inputSettings={inputSettings}
testId={`${testIds.TEXT_EDITOR_CONTROL}:menu-${groupIndex}-${index}`}
disabled={disabled}
/>
) : "MenuButton" in control ? (
<ControlMenuDropdown
key={index}
editor={editor}
controls={control.controls}
MenuButton={control.MenuButton}
inputSettings={inputSettings}
disabled={disabled}
/>
) : "Component" in control ? (
<control.Component
{...control}
key={index}
editor={editor}
inputSettings={inputSettings}
data-testid={`${testIds.TEXT_EDITOR_CONTROL}:${control.name}`}
disabled={disabled}
/>
) : (
<ControlButton
key={control.name}
onClick={(event) => control.cmd({ editor, inputSettings, event })}
label={control.label}
shortcut={control.shortcut}
active={editor.isActive(control.name)}
square
data-testid={`${testIds.TEXT_EDITOR_CONTROL}:${control.name}`}
disabled={disabled}
>
<control.Icon />
</ControlButton>
))}
</ControlGroup>
))}
</ControlGroups>
<ToolbarOverflow>
<ToolbarOffset>
{controlRows.map((controlRow, index) => (
<ToolbarControlRow
controls={controlRow}
key={index}
{...props}
/>
))}
</ToolbarOffset>
</ToolbarOverflow>
);
}
Loading

0 comments on commit 9b61615

Please sign in to comment.