Separate Client Portal & Dashboard
This commit is contained in:
177
frontend/dashboard/src/components/editor/EditorToolbar.tsx
Normal file
177
frontend/dashboard/src/components/editor/EditorToolbar.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import { Quill } from 'react-quill';
|
||||
// components
|
||||
import Iconify from '../Iconify';
|
||||
//
|
||||
import EditorToolbarStyle from './EditorToolbarStyle';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const FONT_FAMILY = ['Arial', 'Tahoma', 'Georgia', 'Impact', 'Verdana'];
|
||||
|
||||
const FONT_SIZE = [
|
||||
'8px',
|
||||
'9px',
|
||||
'10px',
|
||||
'12px',
|
||||
'14px',
|
||||
'16px',
|
||||
'20px',
|
||||
'24px',
|
||||
'32px',
|
||||
'42px',
|
||||
'54px',
|
||||
'68px',
|
||||
'84px',
|
||||
'98px',
|
||||
];
|
||||
const HEADINGS = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6'];
|
||||
|
||||
export function undoChange() {
|
||||
// @ts-ignore
|
||||
this.quill.history.undo();
|
||||
}
|
||||
export function redoChange() {
|
||||
// @ts-ignore
|
||||
this.quill.history.redo();
|
||||
}
|
||||
|
||||
const Size = Quill.import('attributors/style/size');
|
||||
Size.whitelist = FONT_SIZE;
|
||||
Quill.register(Size, true);
|
||||
|
||||
const Font = Quill.import('attributors/style/font');
|
||||
Font.whitelist = FONT_FAMILY;
|
||||
Quill.register(Font, true);
|
||||
|
||||
export const formats = [
|
||||
'align',
|
||||
'background',
|
||||
'blockquote',
|
||||
'bold',
|
||||
'bullet',
|
||||
'code',
|
||||
'code-block',
|
||||
'color',
|
||||
'direction',
|
||||
'font',
|
||||
'formula',
|
||||
'header',
|
||||
'image',
|
||||
'indent',
|
||||
'italic',
|
||||
'link',
|
||||
'list',
|
||||
'script',
|
||||
'size',
|
||||
'strike',
|
||||
'table',
|
||||
'underline',
|
||||
'video',
|
||||
];
|
||||
|
||||
type EditorToolbarProps = {
|
||||
id: string;
|
||||
isSimple?: boolean;
|
||||
};
|
||||
|
||||
export default function EditorToolbar({ id, isSimple, ...other }: EditorToolbarProps) {
|
||||
return (
|
||||
<EditorToolbarStyle {...other}>
|
||||
<div id={id}>
|
||||
<div className="ql-formats">
|
||||
{!isSimple && (
|
||||
<select className="ql-font" defaultValue="">
|
||||
<option value="">Font</option>
|
||||
{FONT_FAMILY.map((font) => (
|
||||
<option key={font} value={font}>
|
||||
{font}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
|
||||
{!isSimple && (
|
||||
<select className="ql-size" defaultValue="16px">
|
||||
{FONT_SIZE.map((size) => (
|
||||
<option key={size} value={size}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
|
||||
<select className="ql-header" defaultValue="">
|
||||
{HEADINGS.map((heading, index) => (
|
||||
<option key={heading} value={index + 1}>
|
||||
{heading}
|
||||
</option>
|
||||
))}
|
||||
<option value="">Normal</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="ql-formats">
|
||||
<button type="button" className="ql-bold" />
|
||||
<button type="button" className="ql-italic" />
|
||||
<button type="button" className="ql-underline" />
|
||||
<button type="button" className="ql-strike" />
|
||||
</div>
|
||||
|
||||
{!isSimple && (
|
||||
<div className="ql-formats">
|
||||
<select className="ql-color" />
|
||||
<select className="ql-background" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="ql-formats">
|
||||
<button type="button" className="ql-list" value="ordered" />
|
||||
<button type="button" className="ql-list" value="bullet" />
|
||||
{!isSimple && <button type="button" className="ql-indent" value="-1" />}
|
||||
{!isSimple && <button type="button" className="ql-indent" value="+1" />}
|
||||
</div>
|
||||
|
||||
{!isSimple && (
|
||||
<div className="ql-formats">
|
||||
<button type="button" className="ql-script" value="super" />
|
||||
<button type="button" className="ql-script" value="sub" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isSimple && (
|
||||
<div className="ql-formats">
|
||||
<button type="button" className="ql-code-block" />
|
||||
<button type="button" className="ql-blockquote" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="ql-formats">
|
||||
<button type="button" className="ql-direction" value="rtl" />
|
||||
<select className="ql-align" />
|
||||
</div>
|
||||
|
||||
<div className="ql-formats">
|
||||
<button type="button" className="ql-link" />
|
||||
<button type="button" className="ql-image" />
|
||||
<button type="button" className="ql-video" />
|
||||
</div>
|
||||
|
||||
<div className="ql-formats">
|
||||
{!isSimple && <button type="button" className="ql-formula" />}
|
||||
<button type="button" className="ql-clean" />
|
||||
</div>
|
||||
|
||||
{!isSimple && (
|
||||
<div className="ql-formats">
|
||||
<button type="button" className="ql-undo">
|
||||
<Iconify icon={'ic:round-undo'} width={18} height={18} />
|
||||
</button>
|
||||
<button type="button" className="ql-redo">
|
||||
<Iconify icon={'ic:round-redo'} width={18} height={18} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</EditorToolbarStyle>
|
||||
);
|
||||
}
|
||||
133
frontend/dashboard/src/components/editor/EditorToolbarStyle.tsx
Normal file
133
frontend/dashboard/src/components/editor/EditorToolbarStyle.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const EditorToolbarStyle = styled('div')(({ theme }) => {
|
||||
const isRTL = theme.direction === 'rtl';
|
||||
|
||||
return {
|
||||
'& .ql-snow.ql-toolbar button:hover .ql-fill, .ql-snow .ql-toolbar button:hover .ql-fill, .ql-snow.ql-toolbar button:focus .ql-fill, .ql-snow .ql-toolbar button:focus .ql-fill, .ql-snow.ql-toolbar button.ql-active .ql-fill, .ql-snow .ql-toolbar button.ql-active .ql-fill, .ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill, .ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill, .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill, .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill, .ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill, .ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill, .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill, .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill, .ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill, .ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill, .ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill, .ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill, .ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill, .ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill, .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill, .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill, .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill, .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill, .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill':
|
||||
{
|
||||
fill: theme.palette.primary.main,
|
||||
},
|
||||
'& .ql-snow.ql-toolbar button:hover, .ql-snow .ql-toolbar button:hover, .ql-snow.ql-toolbar button:focus, .ql-snow .ql-toolbar button:focus, .ql-snow.ql-toolbar button.ql-active, .ql-snow .ql-toolbar button.ql-active, .ql-snow.ql-toolbar .ql-picker-label:hover, .ql-snow .ql-toolbar .ql-picker-label:hover, .ql-snow.ql-toolbar .ql-picker-label.ql-active, .ql-snow .ql-toolbar .ql-picker-label.ql-active, .ql-snow.ql-toolbar .ql-picker-item:hover, .ql-snow .ql-toolbar .ql-picker-item:hover, .ql-snow.ql-toolbar .ql-picker-item.ql-selected, .ql-snow .ql-toolbar .ql-picker-item.ql-selected':
|
||||
{
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
'& .ql-snow.ql-toolbar button:hover .ql-stroke, .ql-snow .ql-toolbar button:hover .ql-stroke, .ql-snow.ql-toolbar button:focus .ql-stroke, .ql-snow .ql-toolbar button:focus .ql-stroke, .ql-snow.ql-toolbar button.ql-active .ql-stroke, .ql-snow .ql-toolbar button.ql-active .ql-stroke, .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke, .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke, .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke, .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke, .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke, .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, .ql-snow.ql-toolbar button:hover .ql-stroke-miter, .ql-snow .ql-toolbar button:hover .ql-stroke-miter, .ql-snow.ql-toolbar button:focus .ql-stroke-miter, .ql-snow .ql-toolbar button:focus .ql-stroke-miter, .ql-snow.ql-toolbar button.ql-active .ql-stroke-miter, .ql-snow .ql-toolbar button.ql-active .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter':
|
||||
{
|
||||
stroke: theme.palette.primary.main,
|
||||
},
|
||||
'& .ql-stroke': {
|
||||
stroke: theme.palette.text.primary,
|
||||
},
|
||||
'& .ql-fill, .ql-stroke.ql-fill': {
|
||||
fill: theme.palette.text.primary,
|
||||
},
|
||||
'& .ql-picker, .ql-picker-options, .ql-picker-item, .ql-picker-label, button': {
|
||||
'&:focus': { outline: 'none' },
|
||||
},
|
||||
'& .ql-toolbar.ql-snow': {
|
||||
border: 'none',
|
||||
borderBottom: `solid 1px ${theme.palette.grey[500_32]}`,
|
||||
'& .ql-formats': {
|
||||
'&:not(:last-of-type)': {
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
|
||||
// Button
|
||||
'& button': {
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 4,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
|
||||
// Icon svg
|
||||
'& button svg, span svg': {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
|
||||
// Select
|
||||
'& .ql-picker-label': {
|
||||
...theme.typography.subtitle2,
|
||||
color: theme.palette.text.primary,
|
||||
'& .ql-stroke': {
|
||||
stroke: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
'& .ql-picker-label svg': {
|
||||
...(isRTL && {
|
||||
right: '0 !important',
|
||||
left: 'auto !important',
|
||||
}),
|
||||
},
|
||||
'& .ql-color,& .ql-background,& .ql-align ': {
|
||||
'& .ql-picker-label': {
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
},
|
||||
'& .ql-expanded': {
|
||||
'& .ql-picker-label': {
|
||||
borderRadius: 4,
|
||||
color: theme.palette.text.disabled,
|
||||
borderColor: 'transparent !important',
|
||||
backgroundColor: theme.palette.action.focus,
|
||||
'& .ql-stroke': { stroke: theme.palette.text.disabled },
|
||||
},
|
||||
'& .ql-picker-options': {
|
||||
padding: 0,
|
||||
marginTop: 4,
|
||||
border: 'none',
|
||||
maxHeight: 200,
|
||||
overflow: 'auto',
|
||||
boxShadow: theme.customShadows.z20,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
'& .ql-picker-item': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
|
||||
// Align
|
||||
'&.ql-align': {
|
||||
'& .ql-picker-options': { padding: 0, display: 'flex' },
|
||||
'& .ql-picker-item': {
|
||||
width: 32,
|
||||
height: 32,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
},
|
||||
// Color & Background
|
||||
'&.ql-color, &.ql-background': {
|
||||
'& .ql-picker-options': { padding: 8 },
|
||||
'& .ql-picker-item': {
|
||||
margin: 3,
|
||||
borderRadius: 4,
|
||||
'&.ql-selected': { border: 'solid 1px black' },
|
||||
},
|
||||
},
|
||||
// Font, Size, Header
|
||||
'&.ql-font, &.ql-size, &.ql-header': {
|
||||
'& .ql-picker-options': {
|
||||
padding: theme.spacing(1, 0),
|
||||
},
|
||||
'& .ql-picker-item': {
|
||||
padding: theme.spacing(0.5, 1.5),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default EditorToolbarStyle;
|
||||
97
frontend/dashboard/src/components/editor/index.tsx
Normal file
97
frontend/dashboard/src/components/editor/index.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { ReactNode } from 'react';
|
||||
import ReactQuill, { ReactQuillProps } from 'react-quill';
|
||||
// @mui
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { Box, BoxProps } from '@mui/material';
|
||||
//
|
||||
import EditorToolbar, { formats, redoChange, undoChange } from './EditorToolbar';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const RootStyle = styled(Box)(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: `solid 1px ${theme.palette.grey[500_32]}`,
|
||||
'& .ql-container.ql-snow': {
|
||||
borderColor: 'transparent',
|
||||
...theme.typography.body1,
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
},
|
||||
'& .ql-editor': {
|
||||
minHeight: 200,
|
||||
'&.ql-blank::before': {
|
||||
fontStyle: 'normal',
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
'& pre.ql-syntax': {
|
||||
...theme.typography.body2,
|
||||
padding: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: theme.palette.grey[900],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export interface Props extends ReactQuillProps {
|
||||
id?: string;
|
||||
error?: boolean;
|
||||
simple?: boolean;
|
||||
helperText?: ReactNode;
|
||||
sx?: BoxProps;
|
||||
}
|
||||
|
||||
export default function Editor({
|
||||
id = 'minimal-quill',
|
||||
error,
|
||||
value,
|
||||
onChange,
|
||||
simple = false,
|
||||
helperText,
|
||||
sx,
|
||||
...other
|
||||
}: Props) {
|
||||
const modules = {
|
||||
toolbar: {
|
||||
container: `#${id}`,
|
||||
handlers: {
|
||||
undo: undoChange,
|
||||
redo: redoChange,
|
||||
},
|
||||
},
|
||||
history: {
|
||||
delay: 500,
|
||||
maxStack: 100,
|
||||
userOnly: true,
|
||||
},
|
||||
syntax: true,
|
||||
clipboard: {
|
||||
matchVisual: false,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RootStyle
|
||||
sx={{
|
||||
...(error && {
|
||||
border: (theme) => `solid 1px ${theme.palette.error.main}`,
|
||||
}),
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<EditorToolbar id={id} isSimple={simple} />
|
||||
<ReactQuill
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
modules={modules}
|
||||
formats={formats}
|
||||
placeholder="Write something awesome..."
|
||||
{...other}
|
||||
/>
|
||||
</RootStyle>
|
||||
|
||||
{helperText && helperText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user