Skip to content

[WC-3037] RichText: Modal form orientation #1811

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/rich-text-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- We added support to change the form orientation of the link, image and video modals

## [4.8.1] - 2025-07-29

### Fixed
Expand Down
8 changes: 8 additions & 0 deletions packages/pluggableWidgets/rich-text-web/src/RichText.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
<enumerationValue key="readPanel">Read panel</enumerationValue>
</enumerationValues>
</property>
<property key="formOrientation" type="enumeration" defaultValue="horizontal" required="true">
<caption>Form orientation</caption>
<description>The form orientation for modals (Insert link, Insert image, Insert video).</description>
<enumerationValues>
<enumerationValue key="horizontal">Horizontal</enumerationValue>
<enumerationValue key="vertical">Vertical</enumerationValue>
</enumerationValues>
</property>
</propertyGroup>
<propertyGroup caption="Visibility">
<systemProperty key="Visibility" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ describe("Rich Text", () => {
minHeight: 75,
OverflowY: "auto",
customFonts: [],
enableDefaultUpload: true
enableDefaultUpload: true,
formOrientation: "vertical"
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { type VideoFormType } from "../ModalDialog/VideoDialog";
import { Delta } from "quill/core";
import { IMG_MIME_TYPES } from "./constants";
import Emitter from "quill/core/emitter";
import { RichTextContainerProps } from "typings/RichTextProps";

type ModalReturnType = {
showDialog: boolean;
Expand All @@ -23,7 +24,13 @@ type ModalReturnType = {
customImageUploadHandler(value: any): void;
};

export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnType {
export function useEmbedModal(
ref: MutableRefObject<Quill | null>,
props: Pick<
RichTextContainerProps,
"imageSource" | "imageSourceContent" | "enableDefaultUpload" | "formOrientation"
>
): ModalReturnType {
const [showDialog, setShowDialog] = useState<boolean>(false);
const [dialogConfig, setDialogConfig] = useState<ChildDialogProps>({});
const openDialog = (): void => {
Expand Down Expand Up @@ -51,7 +58,8 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
closeDialog();
},
onClose: closeDialog,
defaultValue: { ...value, text }
defaultValue: { ...value, text },
formOrientation: props.formOrientation
}
});
openDialog();
Expand Down Expand Up @@ -113,7 +121,8 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
},
onClose: closeDialog,
selection: ref.current?.getSelection(),
defaultValue: { ...value }
defaultValue: { ...value },
formOrientation: props.formOrientation
}
});
openDialog();
Expand All @@ -136,7 +145,8 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
}
closeDialog();
},
onClose: closeDialog
onClose: closeDialog,
formOrientation: props.formOrientation
}
});
openDialog();
Expand Down Expand Up @@ -186,7 +196,11 @@ export function useEmbedModal(ref: MutableRefObject<Quill | null>): ModalReturnT
closeDialog();
},
onClose: closeDialog,
defaultValue: { ...value }
defaultValue: { ...value },
formOrientation: props.formOrientation,
imageSource: props.imageSource,
imageSourceContent: props.imageSourceContent,
enableDefaultUpload: props.enableDefaultUpload
}
});
openDialog();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface EditorProps
defaultValue?: string;
onTextChange?: (...args: [delta: Delta, oldContent: Delta, source: EmitterSource]) => void;
onSelectionChange?: (...args: [range: Range, oldRange: Range, source: EmitterSource]) => void;
formOrientation: "horizontal" | "vertical";
theme: string;
style?: CSSProperties;
className?: string;
Expand Down Expand Up @@ -65,7 +66,7 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
customVideoHandler,
customViewCodeHandler,
customImageUploadHandler
} = useEmbedModal(ref);
} = useEmbedModal(ref, props);
const customIndentHandler = getIndentHandler(ref);

// quill instance is not changing, thus, the function reference has to stays.
Expand Down Expand Up @@ -213,9 +214,6 @@ const Editor = forwardRef((props: EditorProps, ref: MutableRefObject<Quill | nul
isOpen={showDialog}
onOpenChange={open => setShowDialog(open)}
parentNode={modalRef.current?.ownerDocument.body}
imageSource={props.imageSource}
imageSourceContent={props.imageSourceContent}
enableDefaultUpload={props.enableDefaultUpload}
{...dialogConfig}
></Dialog>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
tabIndex,
imageSource,
imageSourceContent,
enableDefaultUpload
enableDefaultUpload,
formOrientation
} = props;

const globalState = useContext(EditorContext);
Expand Down Expand Up @@ -215,6 +216,7 @@ function EditorWrapperInner(props: EditorWrapperProps): ReactElement {
imageSource={imageSource}
imageSourceContent={imageSourceContent}
enableDefaultUpload={enableDefaultUpload}
formOrientation={formOrientation}
/>
</div>
{enableStatusBar && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import {
useRole
} from "@floating-ui/react";
import { If } from "@mendix/widget-plugin-component-kit/If";
import classNames from "classnames";
import { createElement, Fragment, ReactElement } from "react";
import LinkDialog, { LinkDialogProps } from "./LinkDialog";
import VideoDialog, { VideoDialogProps } from "./VideoDialog";
import ViewCodeDialog, { ViewCodeDialogProps } from "./ViewCodeDialog";
import ImageDialog, { ImageDialogProps } from "./ImageDialog";
import "./Dialog.scss";
import { RichTextContainerProps } from "../../../typings/RichTextProps";

interface BaseDialogProps {
isOpen: boolean;
Expand Down Expand Up @@ -49,15 +49,13 @@ export type ChildDialogProps =
| ViewCodeDialogBaseProps
| ImageDialogBaseProps;

export type DialogProps = BaseDialogProps &
ChildDialogProps &
Pick<RichTextContainerProps, "imageSource" | "imageSourceContent" | "enableDefaultUpload">;
export type DialogProps = BaseDialogProps & ChildDialogProps;

/**
* Dialog components that will be shown on toolbar's button
*/
export default function Dialog(props: DialogProps): ReactElement {
const { isOpen, onOpenChange, dialogType, config, imageSource, imageSourceContent, enableDefaultUpload } = props;
const { isOpen, onOpenChange, dialogType, config } = props;
const { refs, context } = useFloating({
open: isOpen,
onOpenChange
Expand All @@ -81,7 +79,9 @@ export default function Dialog(props: DialogProps): ReactElement {
></FloatingOverlay>
<FloatingFocusManager context={context}>
<div
className="Dialog mx-layoutgrid widget-rich-text"
className={classNames("Dialog mx-layoutgrid widget-rich-text", {
"form-vertical": config?.formOrientation === "vertical"
})}
ref={refs.setFloating}
aria-labelledby={dialogType}
aria-describedby={dialogType}
Expand All @@ -97,12 +97,7 @@ export default function Dialog(props: DialogProps): ReactElement {
<ViewCodeDialog {...(config as ViewCodeDialogProps)}></ViewCodeDialog>
</If>
<If condition={dialogType === "image"}>
<ImageDialog
imageSource={imageSource}
imageSourceContent={imageSourceContent}
enableDefaultUpload={enableDefaultUpload}
{...(config as ImageDialogProps)}
></ImageDialog>
<ImageDialog {...(config as ImageDialogProps)}></ImageDialog>
</If>
</div>
</FloatingFocusManager>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,50 @@ export function DialogHeader(props: DialogHeaderProps): ReactElement {
);
}

export function DialogBody(props: PropsWithChildrenWithClass): ReactElement {
const { children, className } = props;
export function DialogBody(
props: PropsWithChildrenWithClass & { formOrientation: "horizontal" | "vertical" }
): ReactElement {
const { children, className, formOrientation } = props;

return <div className={classNames("widget-rich-text-modal-content form-horizontal", className)}>{children}</div>;
return (
<div
className={classNames(
"widget-rich-text-modal-content",
formOrientation === "vertical" ? "form-vertical" : "form-horizontal",
className
)}
>
{children}
</div>
);
}

export interface FormControlProps extends PropsWithChildrenWithClass {
label?: string;
formOrientation: "horizontal" | "vertical";
inputId?: string;
}

export function FormControl(props: FormControlProps): ReactElement {
const { children, className, label } = props;
const { children, className, label, formOrientation, inputId } = props;

return (
<If condition={children !== undefined && children !== null}>
<div className={classNames("form-group", className)}>
{label && <label className="control-label col-sm-3">{label}</label>}
<div className={`col-sm-${label ? "9" : "12"}`}> {children}</div>
<div className={classNames("form-group", { "no-columns": formOrientation === "vertical" }, className)}>
{label && (
<label
htmlFor={inputId}
id={`${inputId}-label`}
className={classNames("control-label", formOrientation !== "vertical" && "col-sm-3")}
>
{label}
</label>
)}
{formOrientation === "vertical" ? (
children
) : (
<div className={`col-sm-${label ? "9" : "12"}`}>{children}</div>
)}
</div>
</If>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ interface CustomEvent<T = any> extends Event {
initCustomEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, detailArg: T): void;
}

export interface ImageDialogProps extends Pick<RichTextContainerProps, "imageSource" | "imageSourceContent"> {
export interface ImageDialogProps
extends Pick<
RichTextContainerProps,
"imageSource" | "imageSourceContent" | "enableDefaultUpload" | "formOrientation"
> {
onSubmit(value: imageConfigType): void;
onClose(): void;
defaultValue?: imageConfigType;
enableDefaultUpload?: boolean;
}

export default function ImageDialog(props: ImageDialogProps): ReactElement {
const { onClose, defaultValue, onSubmit, imageSource, imageSourceContent, enableDefaultUpload } = props;
const { onClose, defaultValue, onSubmit, imageSource, imageSourceContent, enableDefaultUpload, formOrientation } =
props;
const [activeTab, setActiveTab] = useState("general");
const [selectedImageEntity, setSelectedImageEntity] = useState<Image>();
const imageUploadElementRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -127,9 +131,9 @@ export default function ImageDialog(props: ImageDialogProps): ReactElement {
}, [imageUploadElementRef.current]);

return (
<DialogContent className="image-dialog">
<DialogContent className={classNames("image-dialog", formOrientation === "vertical" ? "form-vertical" : "")}>
<DialogHeader onClose={onClose}>{activeTab === "general" ? "Insert/Edit" : "Embed"} Images</DialogHeader>
<DialogBody>
<DialogBody formOrientation={formOrientation}>
<div ref={imageUploadElementRef}>
{!disableEmbed && (
<div>
Expand Down Expand Up @@ -161,7 +165,11 @@ export default function ImageDialog(props: ImageDialogProps): ReactElement {
)}
<div>
<If condition={activeTab === "general"}>
<FormControl label="Source">
<FormControl
label="Source"
formOrientation={formOrientation}
inputId="rich-text-image-file-input"
>
{defaultValue?.src ? (
<img
src={defaultValue.src}
Expand All @@ -182,6 +190,7 @@ export default function ImageDialog(props: ImageDialogProps): ReactElement {
</div>
) : enableDefaultUpload ? (
<input
id="rich-text-image-file-input"
name="files"
className="form-control mx-textarea-input mx-textarea-noresize code-input"
type="file"
Expand All @@ -190,8 +199,13 @@ export default function ImageDialog(props: ImageDialogProps): ReactElement {
></input>
) : undefined}
</FormControl>
<FormControl label="Alternative description">
<FormControl
label="Alternative description"
formOrientation={formOrientation}
inputId="rich-text-image-alt-input"
>
<input
id="rich-text-image-alt-input"
className="form-control"
type="text"
name="alt"
Expand All @@ -200,10 +214,16 @@ export default function ImageDialog(props: ImageDialogProps): ReactElement {
ref={inputReference}
/>
</FormControl>
<FormControl label="Width" className="image-dialog-size">
<FormControl
label="Width"
className="image-dialog-size"
formOrientation={formOrientation}
inputId="rich-text-image-width-input"
>
<div className="flexcontainer image-dialog-size-container">
<div className="flexcontainer image-dialog-size-input">
<input
id="rich-text-image-width-input"
className="form-control"
type="number"
name="width"
Expand All @@ -217,6 +237,7 @@ export default function ImageDialog(props: ImageDialogProps): ReactElement {
</div>
<div className="flexcontainer image-dialog-size-input">
<input
id="rich-text-image-height-input"
className="form-control"
type="number"
name="height"
Expand All @@ -228,8 +249,13 @@ export default function ImageDialog(props: ImageDialogProps): ReactElement {
</div>
</div>
</FormControl>
<FormControl label="Keep ratio">
<FormControl
label="Keep ratio"
formOrientation={formOrientation}
inputId="rich-text-image-keep-ratio-input"
>
<input
id="rich-text-image-keep-ratio-input"
type="checkbox"
name="keepAspectRatio"
checked={formState.keepAspectRatio}
Expand Down
Loading
Loading