Skip to content

Commit

Permalink
feat(admin-ui): add RichTextEditor component (#4544)
Browse files Browse the repository at this point in the history
  • Loading branch information
leopuleo authored Mar 6, 2025
1 parent c8d9192 commit 8ef1d9c
Show file tree
Hide file tree
Showing 16 changed files with 488 additions and 194 deletions.
1 change: 1 addition & 0 deletions packages/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"description": "The UI component library for Webiny's Admin app.",
"license": "MIT",
"dependencies": {
"@editorjs/editorjs": "2.26.5",
"@fortawesome/fontawesome-svg-core": "^1.3.0",
"@fortawesome/react-fontawesome": "^0.1.17",
"@material-design-icons/svg": "^0.14.13",
Expand Down
84 changes: 84 additions & 0 deletions packages/admin-ui/src/RichTextEditor/RichTextEditor.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { RichTextEditor } from "~/RichTextEditor";

const meta: Meta<typeof RichTextEditor> = {
title: "Components/Form/RichTextEditor",
component: RichTextEditor,
argTypes: {
onChange: { action: "onChange" },
onReady: { action: "onReady" }
},
parameters: {
layout: "padded"
},
render: args => {
const [value, setValue] = useState(args.value);
return <RichTextEditor {...args} value={value} onChange={setValue} />;
}
};

export default meta;
type Story = StoryObj<typeof RichTextEditor>;

export const Default: Story = {};

export const WithLabel: Story = {
args: {
label: "Any field label"
}
};

export const WithLabelRequired: Story = {
args: {
...Default.args,
label: "Any field label",
required: true
}
};

export const WithDescription: Story = {
args: {
...Default.args,
description: "Provide the required information for processing your request."
}
};

export const WithNotes: Story = {
args: {
...Default.args,
note: "Note: Ensure your selection or input is accurate before proceeding."
}
};

export const WithErrors: Story = {
args: {
...Default.args,
validation: {
isValid: false,
message: "This field is required."
}
}
};

export const Disabled: Story = {
args: {
...Default.args,
label: "Any field label",
disabled: true
}
};

export const FullExample: Story = {
args: {
...Default.args,
label: "Any field label",
required: true,
description: "Provide the required information for processing your request.",
note: "Note: Ensure your selection or input is accurate before proceeding.",
validation: {
isValid: false,
message: "This field is required."
}
}
};
38 changes: 38 additions & 0 deletions packages/admin-ui/src/RichTextEditor/RichTextEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useMemo } from "react";
import { makeDecoratable } from "~/utils";
import { RichTextEditorPrimitive, RichTextEditorPrimitiveProps } from "./RichTextEditorPrimitive";
import {
FormComponentDescription,
FormComponentErrorMessage,
FormComponentLabel,
FormComponentNote,
FormComponentProps
} from "~/FormComponent";

type RichTextEditorProps = RichTextEditorPrimitiveProps & FormComponentProps;

const DecoratableRichTextEditor = ({
label,
description,
note,
required,
disabled,
validation,
...props
}: RichTextEditorProps) => {
const { isValid: validationIsValid, message: validationMessage } = validation || {};
const invalid = useMemo(() => validationIsValid === false, [validationIsValid]);

return (
<div className={"wby-w-full"}>
<FormComponentLabel text={label} required={required} disabled={disabled} />
<FormComponentDescription text={description} />
<RichTextEditorPrimitive {...props} disabled={disabled} invalid={invalid} />
<FormComponentErrorMessage text={validationMessage} invalid={invalid} />
<FormComponentNote text={note} />
</div>
);
};
const RichTextEditor = makeDecoratable("RichTextEditor", DecoratableRichTextEditor);

export { RichTextEditor, type RichTextEditorProps };
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { Meta, StoryObj } from "@storybook/react";
import { RichTextEditorPrimitive } from "./RichTextEditorPrimitive";

const meta: Meta<typeof RichTextEditorPrimitive> = {
title: "Components/Form Primitives/RichTextEditor",
component: RichTextEditorPrimitive,
tags: ["autodocs"],
argTypes: {
onChange: { action: "onChange" },
onReady: { action: "onReady" }
},
parameters: {
layout: "padded"
}
};

export default meta;
type Story = StoryObj<typeof RichTextEditorPrimitive>;

export const Default: Story = {};

export const MediumSize: Story = {
args: {
placeholder: "Custom placeholder",
size: "md"
}
};

export const LargeSize: Story = {
args: {
placeholder: "Custom placeholder",
size: "lg"
}
};

export const ExtraLargeSize: Story = {
args: {
placeholder: "Custom placeholder",
size: "xl"
}
};

export const PrimaryVariant: Story = {
args: {
variant: "primary",
placeholder: "Custom placeholder"
}
};

export const PrimaryVariantDisabled: Story = {
args: {
...PrimaryVariant.args,
disabled: true
}
};

export const PrimaryVariantInvalid: Story = {
args: {
...PrimaryVariant.args,
invalid: true
}
};

export const SecondaryVariant: Story = {
args: {
variant: "secondary",
placeholder: "Custom placeholder"
}
};

export const SecondaryVariantDisabled: Story = {
args: {
...SecondaryVariant.args,
disabled: true
}
};

export const SecondaryVariantInvalid: Story = {
args: {
...SecondaryVariant.args,
invalid: true
}
};

export const GhostVariant: Story = {
args: {
variant: "ghost",
placeholder: "Custom placeholder"
}
};

export const GhostVariantDisabled: Story = {
args: {
...GhostVariant.args,
disabled: true
}
};

export const GhostVariantInvalid: Story = {
args: {
...GhostVariant.args,
invalid: true
}
};

export const WithCustomValue: Story = {
args: {
...Default.args,
value: [
{
type: "paragraph",
data: { text: "Custom value", id: "any-custom-id", type: "paragraph" }
}
]
}
};
Loading

0 comments on commit 8ef1d9c

Please sign in to comment.