Skip to content

Commit

Permalink
feat: rich editor component
Browse files Browse the repository at this point in the history
  • Loading branch information
abouolia committed Dec 27, 2023
1 parent c469480 commit a676e09
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 8 deletions.
52 changes: 52 additions & 0 deletions packages/webapp/src/components/Forms/FRichEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { FieldConfig, FieldProps } from 'formik';
import { Field } from '@blueprintjs-formik/core';
import { RichEditor, RichEditorProps } from '../../components/RichEditor';

export interface FRichEditorProps
extends Omit<FieldConfig, 'children' | 'component' | 'as'>,
RichEditorProps {
name: string;
value?: string;
}

interface FieldToRichEditorProps
extends FieldProps,
Omit<RichEditorProps, 'form'> {}

/**
* Transformes the field props to `RichEditor` props.
* @param {FieldToRichEditorProps}
* @returns {HTMLSelectProps}
*/
function fieldToRichEditor({
field: { onBlur: onFieldBlur, ...field },
form: { touched, errors, ...form },
...props
}: FieldToRichEditorProps): RichEditorProps {
return {
...field,
...props,
onChange: (value: string) => {
form.setFieldValue(field.name, value);
},
};
}

/**
* Transformes field props to `RichEditor` props.
* @param {FieldToRichEditorProps}
* @returns {JSX.Element}
*/
function FieldToRichEditor({ ...props }: FieldToRichEditorProps): JSX.Element {
return <RichEditor {...fieldToRichEditor(props)} />;
}

/**
* Rich editor wrapper to bind with Formik.
* @param {FRichEditorProps} props -
* @returns {JSX.Element}
*/
export function FRichEditor({ ...props }: FRichEditorProps): JSX.Element {
return <Field {...props} component={FieldToRichEditor} />;
}
3 changes: 2 additions & 1 deletion packages/webapp/src/components/Forms/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './FMoneyInputGroup';
export * from './BlueprintFormik';
export * from './InputPrependText';
export * from './InputPrependButton';
export * from './MoneyInputGroup';
export * from './MoneyInputGroup';
export * from './FRichEditor';
66 changes: 66 additions & 0 deletions packages/webapp/src/components/RichEditor/RichEditor.style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Basic editor styles */
.tiptap {
color: #222;

&:focus-visible {
outline: none;
}

>*+* {
margin-top: 0.75em;
}

ul,
ol {
padding: 0 1rem;
}

h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}

code {
background: rgba(#ffffff, 0.1);
color: rgba(#ffffff, 0.6);
border: 1px solid rgba(#ffffff, 0.1);
border-radius: 0.5rem;
padding: 0.2rem;
}

pre {
background: rgba(#ffffff, 0.1);
font-family: "JetBrainsMono", monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;

code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
border: none;
}
}

img {
max-width: 100%;
height: auto;
}

blockquote {
margin-left: 0;
padding-left: 1rem;
border-left: 2px solid rgba(#ffffff, 0.4);

hr {
border: none;
border-top: 2px solid rgba(#ffffff, 0.1);
margin: 2rem 0;
}
}
}
58 changes: 58 additions & 0 deletions packages/webapp/src/components/RichEditor/RichEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// @ts-nocheck
import { Color } from '@tiptap/extension-color';
import ListItem from '@tiptap/extension-list-item';
import TextStyle from '@tiptap/extension-text-style';
import { EditorProvider } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { useUncontrolled } from '@/hooks/useUncontrolled';
import { Box } from '../Layout/Box';
import './RichEditor.style.scss';

const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false,
},
orderedList: {
keepMarks: true,
keepAttributes: false,
},
}),
];

export interface RichEditorProps {
value?: string;
initialValue?: string;
onChange?: (value: string) => void;
className?: string;
}
export const RichEditor = ({
value,
initialValue,
onChange,
className,
}: RichEditorProps) => {
const [content, handleChange] = useUncontrolled({
value,
initialValue,
onChange,
finalValue: '',
});

const handleBlur = ({ editor }) => {
handleChange(editor.getHTML());
};

return (
<Box className={className}>
<EditorProvider
extensions={extensions}
content={content}
onBlur={handleBlur}
/>
</Box>
);
};
1 change: 1 addition & 0 deletions packages/webapp/src/components/RichEditor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './RichEditor';
63 changes: 63 additions & 0 deletions packages/webapp/src/containers/SendMailNotification/RichEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// @ts-nocheck
import './styles.scss';
import { Color } from '@tiptap/extension-color';
import ListItem from '@tiptap/extension-list-item';
import TextStyle from '@tiptap/extension-text-style';
import { EditorProvider } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { Box } from '@/components';
import styled from 'styled-components';
import { useUncontrolled } from '@/hooks/useUncontrolled';

const extensions = [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit.configure({
bulletList: {
keepMarks: true,
keepAttributes: false,
},
orderedList: {
keepMarks: true,
keepAttributes: false,
},
}),
];

export interface RichEditorProps {
value?: string;
initialValue?: string;
onChange?: (value: string) => void;
className?: string;
}
export const RichEditor = ({
value,
initialValue,
onChange,
className,
}: RichEditorProps) => {
const [content, handleChange] = useUncontrolled({
value,
initialValue,
finalValue: '',
onChange,
});

return (
<Root>
<EditorProvider
extensions={extensions}
content={content}
onBlur={handleChange}
/>
</Root>
);
};

const Root = styled(Box)`
padding: 15px;
border: 1px solid #dedfe9;
border-top: 0;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
// @ts-nocheck
import { Form, useFormikContext } from 'formik';
import { FFormGroup, FInputGroup, FMultiSelect } from '@/components';
import {
FFormGroup,
FInputGroup,
FMultiSelect,
FRichEditor,
FSwitch,
Hint,
} from '@/components';
import styled from 'styled-components';
import { Button, Classes, Intent } from '@blueprintjs/core';
import { Button, Classes, Intent, Position } from '@blueprintjs/core';
import { saveInvoke } from '@/utils';

interface SendMailNotificationFormProps {
Expand All @@ -24,25 +31,47 @@ export function SendMailNotificationForm({
<HeaderBox>
<FFormGroup
label={'From'}
labelInfo={
<Hint
content={'asdasd asdasd asdsad'}
position={Position.BOTTOM_LEFT}
/>
}
name={'from'}
inline={true}
fastField={true}
>
<FMultiSelect
items={[]}
items={[
{
text: '[email protected]',
value: '[email protected]',
},
]}
name={'from'}
placeholder=""
popoverProps={{ minimal: true, fill: true }}
tagInputProps={{
tagProps: { round: true, minimal: true, large: true },
}}
fill={true}
/>
</FFormGroup>

<FFormGroup label={'To'} name={'to'} inline={true} fastField={true}>
<FMultiSelect
items={[]}
items={[
{
text: '[email protected]',
value: '[email protected]',
},
]}
name={'to'}
placeholder=""
popoverProps={{ minimal: true, fill: true }}
tagInputProps={{
tagProps: { round: true, minimal: true, large: true },
}}
fill={true}
/>
</FFormGroup>
Expand All @@ -56,6 +85,12 @@ export function SendMailNotificationForm({
<FInputGroup name={'subject'} fill={true} />
</FFormGroup>
</HeaderBox>

<MailMessageEditor name={'message'} />

<AttachFormGroup name={'attach_invoice'} inline>
<FSwitch name={'attach_invoice'} label={'Attach Invoice'} />
</AttachFormGroup>
</div>

<div className={Classes.DIALOG_FOOTER}>
Expand All @@ -82,16 +117,33 @@ export function SendMailNotificationForm({
);
}

const AttachFormGroup = styled(FFormGroup)`
background: #f8f9fb;
margin-top: 0.6rem;
padding: 4px 14px;
border-radius: 5px;
border: 1px solid #dcdcdd;
`;

const MailMessageEditor = styled(FRichEditor)`
padding: 15px;
border: 1px solid #dedfe9;
border-top: 0;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
`;

const HeaderBox = styled('div')`
border-top-right-radius: 5px;
border-top-left-radius: 5px;
border: 1px solid #dddfe9;
padding: 15px;
border-bottom: 2px solid #eaeaef;
padding: 6px 15px;
.bp4-form-group {
margin: 0;
padding-top: 12px;
padding-bottom: 12px;
padding-top: 8px;
padding-bottom: 8px;
&:not(:last-of-type) {
border-bottom: 1px solid #dddfe9;
Expand All @@ -114,5 +166,19 @@ const HeaderBox = styled('div')`
}
.bp4-input {
border-color: transparent;
padding: 0;
&:focus,
&.bp4-active {
box-shadow: 0 0 0 0;
}
}
.bp4-input-ghost {
margin-top: 5px;
}
.bp4-tag-input-values {
margin: 0;
}
`;

0 comments on commit a676e09

Please sign in to comment.