goobs-frontend 0.8.8 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -16
- package/src/components/Button/index.tsx +134 -110
- package/src/components/{DataGrid/Checkbox → Checkbox}/index.tsx +1 -1
- package/src/components/ComplexTextEditor/MarkdownEditor/index.tsx +107 -0
- package/src/components/ComplexTextEditor/RichEditor/index.tsx +214 -0
- package/src/components/ComplexTextEditor/SimpleEditor/index.tsx +65 -0
- package/src/components/ComplexTextEditor/ToolbarButton/index.tsx +111 -0
- package/src/components/ComplexTextEditor/Toolbars/Complex/index.tsx +128 -0
- package/src/components/ComplexTextEditor/Toolbars/Markdown/index.tsx +325 -0
- package/src/components/ComplexTextEditor/Toolbars/Rich/index.tsx +349 -0
- package/src/components/ComplexTextEditor/index.tsx +102 -0
- package/src/components/ComplexTextEditor/types/index.ts +43 -0
- package/src/components/ComplexTextEditor/utils/useMarkdownEditor.tsx +122 -0
- package/src/components/ComplexTextEditor/utils/useRichtextEditor.tsx +301 -0
- package/src/components/Content/Structure/checkbox/useCheckbox.tsx +65 -0
- package/src/components/Content/Structure/complexeditor/useComplexEditor.tsx +69 -0
- package/src/components/Content/Structure/textfield/useTextField.tsx +7 -2
- package/src/components/Content/Structure/typography/useGridTypography.tsx +2 -0
- package/src/components/Content/index.tsx +11 -0
- package/src/components/DataGrid/ManageColumn/index.tsx +1 -1
- package/src/components/DataGrid/ManageRow/index.tsx +269 -103
- package/src/components/DataGrid/Table/index.tsx +101 -53
- package/src/components/DataGrid/index.tsx +77 -48
- package/src/components/DateField/index.tsx +207 -63
- package/src/components/Dropdown/index.tsx +105 -52
- package/src/components/Form/DataGrid/index.tsx +9 -0
- package/src/components/Form/Popup/index.tsx +30 -63
- package/src/components/Grid/index.tsx +121 -166
- package/src/components/Nav/VerticalVariant/index.tsx +14 -10
- package/src/components/TextField/index.tsx +107 -23
- package/src/components/Toolbar/index.tsx +28 -8
- package/src/components/Typography/index.tsx +8 -4
- package/src/index.ts +3 -0
- package/src/types/react-datepicker.d.ts +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goobs-frontend",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A comprehensive React-based UI library built on Material-UI, offering a wide range of customizable components including grids, typography, buttons, cards, forms, navigation, pricing tables, steppers, tooltips, accordions, and more. Designed for building responsive and consistent user interfaces with advanced features like form validation, theming, and code syntax highlighting.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,31 +22,36 @@
|
|
|
22
22
|
"@emotion/cache": "^11.13.5",
|
|
23
23
|
"@emotion/react": "^11.13.5",
|
|
24
24
|
"@emotion/styled": "^11.13.5",
|
|
25
|
-
"@mui/icons-material": "^6.1.
|
|
26
|
-
"@mui/material": "^6.1.
|
|
25
|
+
"@mui/icons-material": "^6.1.10",
|
|
26
|
+
"@mui/material": "^6.1.10",
|
|
27
27
|
"@types/lodash": "^4.17.13",
|
|
28
|
+
"@types/react-datepicker": "^7.0.0",
|
|
28
29
|
"highlight.js": "^11.10.0",
|
|
29
30
|
"jotai": "^2.10.3",
|
|
30
31
|
"lodash": "^4.17.21",
|
|
31
|
-
"next": "15.0.
|
|
32
|
+
"next": "15.0.4",
|
|
32
33
|
"otplib": "^12.0.1",
|
|
33
34
|
"react-datepicker": "^7.5.0",
|
|
34
|
-
"react-qr-code": "^2.0.15"
|
|
35
|
+
"react-qr-code": "^2.0.15",
|
|
36
|
+
"slate": "^0.112.0",
|
|
37
|
+
"slate-dom": "^0.111.0",
|
|
38
|
+
"slate-history": "^0.110.3",
|
|
39
|
+
"slate-react": "^0.112.0"
|
|
35
40
|
},
|
|
36
41
|
"devDependencies": {
|
|
37
|
-
"@next/eslint-plugin-next": "^15.0.
|
|
38
|
-
"@types/node": "^22.10.
|
|
39
|
-
"@types/react": "
|
|
40
|
-
"@types/react-dom": "^
|
|
41
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
42
|
-
"@typescript-eslint/parser": "^8.
|
|
43
|
-
"eslint": "^9.
|
|
44
|
-
"eslint-config-next": "^15.0.
|
|
42
|
+
"@next/eslint-plugin-next": "^15.0.4",
|
|
43
|
+
"@types/node": "^22.10.1",
|
|
44
|
+
"@types/react": "19.0.1",
|
|
45
|
+
"@types/react-dom": "^19.0.1",
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.17.0",
|
|
47
|
+
"@typescript-eslint/parser": "^8.17.0",
|
|
48
|
+
"eslint": "^9.16.0",
|
|
49
|
+
"eslint-config-next": "^15.0.4",
|
|
45
50
|
"eslint-config-prettier": "^9.1.0",
|
|
46
51
|
"eslint-plugin-prettier": "^5.2.1",
|
|
47
|
-
"prettier": "^3.4.
|
|
48
|
-
"react": "^
|
|
49
|
-
"react-dom": "^
|
|
52
|
+
"prettier": "^3.4.2",
|
|
53
|
+
"react": "^19.0.0",
|
|
54
|
+
"react-dom": "^19.0.0",
|
|
50
55
|
"typescript": "^5.7.2"
|
|
51
56
|
},
|
|
52
57
|
"files": [
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { Button, Box, ButtonProps } from '@mui/material'
|
|
4
4
|
import Typography from '../Typography'
|
|
5
|
+
import { SvgIconProps } from '@mui/material/SvgIcon'
|
|
6
|
+
import { styled } from '@mui/material/styles'
|
|
5
7
|
|
|
6
8
|
export interface CustomButtonProps extends ButtonProps {
|
|
7
9
|
text?: string
|
|
@@ -9,131 +11,153 @@ export interface CustomButtonProps extends ButtonProps {
|
|
|
9
11
|
fontcolor?: string
|
|
10
12
|
fontvariant?: 'merriparagraph' | 'merrihelperfooter'
|
|
11
13
|
width?: string
|
|
14
|
+
height?: string
|
|
12
15
|
disableButton?: 'true' | 'false'
|
|
13
|
-
icon?: React.
|
|
16
|
+
icon?: React.ReactElement<SvgIconProps>
|
|
14
17
|
iconcolor?: string
|
|
15
18
|
iconsize?: string
|
|
16
19
|
iconlocation?: 'left' | 'right' | 'above'
|
|
17
20
|
fontlocation?: 'left' | 'center' | 'right'
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
const StyledButton = styled(Button, {
|
|
24
|
+
shouldForwardProp: prop =>
|
|
25
|
+
prop !== 'backgroundcolor' &&
|
|
26
|
+
prop !== 'iconlocation' &&
|
|
27
|
+
prop !== 'fontlocation',
|
|
28
|
+
})<{
|
|
29
|
+
backgroundcolor?: string
|
|
30
|
+
iconlocation?: 'left' | 'right' | 'above'
|
|
31
|
+
fontlocation?: 'left' | 'center' | 'right'
|
|
32
|
+
}>(({ backgroundcolor, iconlocation, fontlocation }) => ({
|
|
33
|
+
minWidth: 'auto',
|
|
34
|
+
width: '100%',
|
|
35
|
+
height: '40px',
|
|
36
|
+
padding: '8px 16px',
|
|
37
|
+
display: 'flex',
|
|
38
|
+
flexDirection: iconlocation === 'above' ? 'column' : 'row',
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
justifyContent:
|
|
41
|
+
fontlocation === 'left'
|
|
42
|
+
? 'flex-start'
|
|
43
|
+
: fontlocation === 'right'
|
|
44
|
+
? 'flex-end'
|
|
45
|
+
: 'center',
|
|
46
|
+
gap: '8px',
|
|
47
|
+
...(backgroundcolor && {
|
|
48
|
+
backgroundColor: backgroundcolor,
|
|
49
|
+
'&:hover': {
|
|
50
|
+
backgroundColor: backgroundcolor,
|
|
51
|
+
opacity: 0.9,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
'& .MuiButton-startIcon': {
|
|
55
|
+
margin: 0,
|
|
56
|
+
},
|
|
57
|
+
'& .MuiButton-endIcon': {
|
|
58
|
+
margin: 0,
|
|
59
|
+
},
|
|
60
|
+
}))
|
|
38
61
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
const StyledBox = styled(Box)({
|
|
63
|
+
display: 'flex',
|
|
64
|
+
flexDirection: 'column',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
width: '100%',
|
|
67
|
+
height: '40px',
|
|
68
|
+
minWidth: 'fit-content',
|
|
69
|
+
})
|
|
45
70
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
71
|
+
const ContentWrapper = styled(Box)<{
|
|
72
|
+
fontlocation?: 'left' | 'center' | 'right'
|
|
73
|
+
}>(({ fontlocation }) => ({
|
|
74
|
+
display: 'flex',
|
|
75
|
+
alignItems: 'center',
|
|
76
|
+
justifyContent:
|
|
77
|
+
fontlocation === 'left'
|
|
78
|
+
? 'flex-start'
|
|
79
|
+
: fontlocation === 'right'
|
|
80
|
+
? 'flex-end'
|
|
81
|
+
: 'center',
|
|
82
|
+
width: '100%',
|
|
83
|
+
height: '100%',
|
|
84
|
+
gap: '8px',
|
|
85
|
+
}))
|
|
50
86
|
|
|
51
|
-
|
|
87
|
+
const CustomButton: React.FC<CustomButtonProps> = ({
|
|
88
|
+
text,
|
|
89
|
+
variant = 'contained',
|
|
90
|
+
fontvariant = 'merriparagraph',
|
|
91
|
+
onClick,
|
|
92
|
+
fontcolor,
|
|
93
|
+
backgroundcolor,
|
|
94
|
+
width,
|
|
95
|
+
height,
|
|
96
|
+
disableButton,
|
|
97
|
+
icon,
|
|
98
|
+
iconcolor,
|
|
99
|
+
iconsize,
|
|
100
|
+
iconlocation = 'left',
|
|
101
|
+
fontlocation = 'center',
|
|
102
|
+
sx,
|
|
103
|
+
...restProps
|
|
104
|
+
}) => {
|
|
105
|
+
const handleButtonClick = (
|
|
106
|
+
event: React.MouseEvent<HTMLButtonElement>
|
|
107
|
+
): void => {
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
onClick?.(event)
|
|
110
|
+
}
|
|
52
111
|
|
|
53
|
-
|
|
54
|
-
color: iconcolor,
|
|
55
|
-
fontSize: iconsize,
|
|
56
|
-
}
|
|
112
|
+
const isDisabled = disableButton === 'true'
|
|
57
113
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
114
|
+
const IconComponent = icon
|
|
115
|
+
? React.cloneElement(icon, {
|
|
116
|
+
sx: {
|
|
117
|
+
color: iconcolor || 'inherit',
|
|
118
|
+
fontSize: iconsize || '20px',
|
|
119
|
+
minWidth: iconsize || '20px',
|
|
120
|
+
minHeight: iconsize || '20px',
|
|
121
|
+
margin: 0,
|
|
122
|
+
},
|
|
123
|
+
} as Partial<SvgIconProps>)
|
|
124
|
+
: null
|
|
63
125
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
>
|
|
79
|
-
{iconlocation === 'left' && IconComponent}
|
|
80
|
-
<Typography
|
|
81
|
-
fontvariant={fontvariant}
|
|
82
|
-
fontcolor={isDisabled ? 'grey' : fontcolor}
|
|
83
|
-
text={text || ''}
|
|
84
|
-
/>
|
|
85
|
-
{iconlocation === 'right' && IconComponent}
|
|
86
|
-
</Box>
|
|
87
|
-
</>
|
|
88
|
-
)
|
|
126
|
+
const buttonContent = (
|
|
127
|
+
<>
|
|
128
|
+
{iconlocation === 'above' && IconComponent}
|
|
129
|
+
<ContentWrapper fontlocation={fontlocation}>
|
|
130
|
+
{iconlocation === 'left' && IconComponent}
|
|
131
|
+
<Typography
|
|
132
|
+
fontvariant={fontvariant}
|
|
133
|
+
fontcolor={isDisabled ? 'grey' : fontcolor || 'white'}
|
|
134
|
+
text={text || ''}
|
|
135
|
+
/>
|
|
136
|
+
{iconlocation === 'right' && IconComponent}
|
|
137
|
+
</ContentWrapper>
|
|
138
|
+
</>
|
|
139
|
+
)
|
|
89
140
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
141
|
+
return (
|
|
142
|
+
<StyledBox sx={{ width, height }}>
|
|
143
|
+
<StyledButton
|
|
144
|
+
{...restProps}
|
|
145
|
+
variant={variant}
|
|
146
|
+
onClick={handleButtonClick}
|
|
147
|
+
disabled={isDisabled}
|
|
148
|
+
disableElevation
|
|
149
|
+
disableRipple
|
|
150
|
+
fullWidth
|
|
151
|
+
backgroundcolor={backgroundcolor}
|
|
152
|
+
iconlocation={iconlocation}
|
|
153
|
+
fontlocation={fontlocation}
|
|
154
|
+
sx={sx}
|
|
96
155
|
>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...buttonStyle,
|
|
103
|
-
flexDirection: iconlocation === 'above' ? 'column' : 'row',
|
|
104
|
-
justifyContent:
|
|
105
|
-
fontlocation === 'left'
|
|
106
|
-
? 'flex-start'
|
|
107
|
-
: fontlocation === 'right'
|
|
108
|
-
? 'flex-end'
|
|
109
|
-
: 'center',
|
|
110
|
-
}}
|
|
111
|
-
disabled={isDisabled}
|
|
112
|
-
>
|
|
113
|
-
{buttonContent}
|
|
114
|
-
</Button>
|
|
115
|
-
</Box>
|
|
116
|
-
)
|
|
117
|
-
},
|
|
118
|
-
(prevProps, nextProps) => {
|
|
119
|
-
const propsAreEqual =
|
|
120
|
-
prevProps.text === nextProps.text &&
|
|
121
|
-
prevProps.variant === nextProps.variant &&
|
|
122
|
-
prevProps.fontvariant === nextProps.fontvariant &&
|
|
123
|
-
prevProps.onClick === nextProps.onClick &&
|
|
124
|
-
prevProps.fontcolor === nextProps.fontcolor &&
|
|
125
|
-
prevProps.backgroundcolor === nextProps.backgroundcolor &&
|
|
126
|
-
prevProps.width === nextProps.width &&
|
|
127
|
-
prevProps.disableButton === nextProps.disableButton &&
|
|
128
|
-
prevProps.icon === nextProps.icon &&
|
|
129
|
-
prevProps.iconcolor === nextProps.iconcolor &&
|
|
130
|
-
prevProps.iconsize === nextProps.iconsize &&
|
|
131
|
-
prevProps.iconlocation === nextProps.iconlocation &&
|
|
132
|
-
prevProps.fontlocation === nextProps.fontlocation
|
|
133
|
-
|
|
134
|
-
return propsAreEqual
|
|
135
|
-
}
|
|
136
|
-
)
|
|
156
|
+
{buttonContent}
|
|
157
|
+
</StyledButton>
|
|
158
|
+
</StyledBox>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
137
161
|
|
|
138
162
|
CustomButton.displayName = 'CustomButton'
|
|
139
163
|
export default CustomButton
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// File: src/components/RichTextEditor/MarkdownEditor/index.tsx
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
handleSwitchToRichText,
|
|
6
|
+
handleBoldClick,
|
|
7
|
+
handleItalicClick,
|
|
8
|
+
} from '../utils/useMarkdownEditor'
|
|
9
|
+
import Toolbar from '../Toolbars/Markdown'
|
|
10
|
+
import { Box, Divider, TextField } from '@mui/material'
|
|
11
|
+
import { RichTextEditorTypes } from '../types'
|
|
12
|
+
|
|
13
|
+
type MarkdownEditorProps = {
|
|
14
|
+
markdown: string
|
|
15
|
+
setMarkdown: (value: string) => void
|
|
16
|
+
markdownMode: boolean
|
|
17
|
+
setMarkdownMode: (value: boolean) => void
|
|
18
|
+
setNewSlateValue: (value: RichTextEditorTypes['CustomElement'][]) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
|
22
|
+
markdown,
|
|
23
|
+
setMarkdown,
|
|
24
|
+
markdownMode,
|
|
25
|
+
setMarkdownMode,
|
|
26
|
+
setNewSlateValue,
|
|
27
|
+
}) => {
|
|
28
|
+
const [markdownValue, setMarkdownValue] = useState(markdown)
|
|
29
|
+
const [selectedText, setSelectedText] = useState('')
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!markdownMode) {
|
|
33
|
+
console.log('MarkdownEditor: markdownMode = ', markdownMode)
|
|
34
|
+
// Perform any action you want when markdownMode changes to false
|
|
35
|
+
}
|
|
36
|
+
}, [markdownMode])
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (markdown !== markdownValue) {
|
|
40
|
+
setMarkdownValue(markdown)
|
|
41
|
+
}
|
|
42
|
+
}, [markdown, markdownValue])
|
|
43
|
+
|
|
44
|
+
const handleLocalMarkdownChange = (
|
|
45
|
+
event: React.ChangeEvent<HTMLInputElement>
|
|
46
|
+
) => {
|
|
47
|
+
const newValue = event.target.value
|
|
48
|
+
setMarkdownValue(newValue)
|
|
49
|
+
setMarkdown(newValue)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const handleSwitchMode = () => {
|
|
53
|
+
handleSwitchToRichText(
|
|
54
|
+
markdown,
|
|
55
|
+
setNewSlateValue,
|
|
56
|
+
setNewSlateValue,
|
|
57
|
+
setMarkdownMode
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const handleSelect = (event: React.SyntheticEvent<HTMLDivElement>) => {
|
|
62
|
+
const target = event.target as HTMLTextAreaElement
|
|
63
|
+
setSelectedText(
|
|
64
|
+
target.value.substring(target.selectionStart, target.selectionEnd)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Box
|
|
70
|
+
sx={{
|
|
71
|
+
border: '1px solid black',
|
|
72
|
+
borderRadius: '8px',
|
|
73
|
+
width: 'auto',
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<Toolbar
|
|
77
|
+
markdownMode={markdownMode}
|
|
78
|
+
setMarkdownMode={setMarkdownMode}
|
|
79
|
+
setMarkdown={setMarkdown}
|
|
80
|
+
handleBoldClick={() =>
|
|
81
|
+
handleBoldClick(selectedText, markdown, setMarkdown)
|
|
82
|
+
}
|
|
83
|
+
handleItalicClick={() =>
|
|
84
|
+
handleItalicClick(selectedText, markdown, setMarkdown)
|
|
85
|
+
}
|
|
86
|
+
switchModeLabel="RichText Mode"
|
|
87
|
+
onSwitchMode={handleSwitchMode}
|
|
88
|
+
/>
|
|
89
|
+
<Divider sx={{ backgroundColor: 'black' }} />
|
|
90
|
+
<TextField
|
|
91
|
+
fullWidth
|
|
92
|
+
multiline
|
|
93
|
+
variant="standard"
|
|
94
|
+
rows={10}
|
|
95
|
+
value={markdownValue}
|
|
96
|
+
onChange={handleLocalMarkdownChange}
|
|
97
|
+
onSelect={handleSelect}
|
|
98
|
+
sx={{
|
|
99
|
+
boxSizing: 'border-box',
|
|
100
|
+
p: 1,
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
</Box>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default MarkdownEditor
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Slate,
|
|
4
|
+
Editable,
|
|
5
|
+
RenderLeafProps,
|
|
6
|
+
RenderElementProps,
|
|
7
|
+
} from 'slate-react'
|
|
8
|
+
import { Descendant } from 'slate'
|
|
9
|
+
import Toolbar from '../Toolbars/Rich'
|
|
10
|
+
import {
|
|
11
|
+
Box,
|
|
12
|
+
Divider,
|
|
13
|
+
Accordion,
|
|
14
|
+
AccordionSummary,
|
|
15
|
+
AccordionDetails,
|
|
16
|
+
} from '@mui/material'
|
|
17
|
+
import { ExpandMore } from '@mui/icons-material'
|
|
18
|
+
import { useRichTextEditor } from '../utils/useRichtextEditor'
|
|
19
|
+
import { RichTextEditorTypes } from '../types'
|
|
20
|
+
import Typography from '../../Typography'
|
|
21
|
+
|
|
22
|
+
export interface RichTextEditorProps {
|
|
23
|
+
value: Descendant[]
|
|
24
|
+
name?: string
|
|
25
|
+
label?: string
|
|
26
|
+
minRows?: number
|
|
27
|
+
onChange?: () => void
|
|
28
|
+
onSelectionChange?: () => void
|
|
29
|
+
onValueChange?: () => void
|
|
30
|
+
accordion?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const Leaf: React.FC<RenderLeafProps> = ({ attributes, children, leaf }) => {
|
|
34
|
+
const customLeaf = leaf as RichTextEditorTypes['CustomText']
|
|
35
|
+
|
|
36
|
+
if (customLeaf.bold) {
|
|
37
|
+
children = <strong>{children}</strong>
|
|
38
|
+
}
|
|
39
|
+
if (customLeaf.italic) {
|
|
40
|
+
children = <em>{children}</em>
|
|
41
|
+
}
|
|
42
|
+
if (customLeaf.underline) {
|
|
43
|
+
children = <u>{children}</u>
|
|
44
|
+
}
|
|
45
|
+
if (customLeaf.strikethrough) {
|
|
46
|
+
children = <s>{children}</s>
|
|
47
|
+
}
|
|
48
|
+
if (customLeaf.link) {
|
|
49
|
+
children = <a href={customLeaf.link}>{children}</a>
|
|
50
|
+
}
|
|
51
|
+
if (customLeaf.code) {
|
|
52
|
+
children = <code>{children}</code>
|
|
53
|
+
}
|
|
54
|
+
return <span {...attributes}>{children}</span>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function RichTextEditor({
|
|
58
|
+
value,
|
|
59
|
+
onChange,
|
|
60
|
+
label,
|
|
61
|
+
minRows = 5,
|
|
62
|
+
accordion = false,
|
|
63
|
+
}: RichTextEditorProps) {
|
|
64
|
+
const {
|
|
65
|
+
editor,
|
|
66
|
+
markdownMode,
|
|
67
|
+
setMarkdown,
|
|
68
|
+
internalValue,
|
|
69
|
+
handleChange,
|
|
70
|
+
handleBoldClick,
|
|
71
|
+
handleItalicClick,
|
|
72
|
+
insertLink,
|
|
73
|
+
onKeyDown,
|
|
74
|
+
} = useRichTextEditor(value, onChange ? () => onChange() : undefined)
|
|
75
|
+
|
|
76
|
+
const [expanded, setExpanded] = useState(false)
|
|
77
|
+
|
|
78
|
+
const renderElement = useCallback(
|
|
79
|
+
(props: RenderElementProps) => <Element {...props} />,
|
|
80
|
+
[]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const renderLeaf = useCallback(
|
|
84
|
+
(props: RenderLeafProps) => <Leaf {...props} />,
|
|
85
|
+
[]
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const handleAccordionChange = () => {
|
|
89
|
+
setExpanded(!expanded)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Box
|
|
94
|
+
sx={{
|
|
95
|
+
display: 'flex',
|
|
96
|
+
flexDirection: 'column',
|
|
97
|
+
width: '100%',
|
|
98
|
+
justifyContent: 'center',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{accordion ? (
|
|
102
|
+
<Accordion expanded={expanded} onChange={handleAccordionChange}>
|
|
103
|
+
<AccordionSummary expandIcon={<ExpandMore />}>
|
|
104
|
+
<Typography fontvariant="merrih4">{label}</Typography>
|
|
105
|
+
</AccordionSummary>
|
|
106
|
+
<AccordionDetails>
|
|
107
|
+
<Box
|
|
108
|
+
sx={{
|
|
109
|
+
border: '1px solid black',
|
|
110
|
+
borderRadius: '8px',
|
|
111
|
+
width: 'auto',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<Slate
|
|
115
|
+
editor={editor}
|
|
116
|
+
initialValue={internalValue}
|
|
117
|
+
onChange={handleChange}
|
|
118
|
+
>
|
|
119
|
+
<Toolbar
|
|
120
|
+
editor={editor}
|
|
121
|
+
markdownMode={markdownMode}
|
|
122
|
+
setMarkdown={setMarkdown}
|
|
123
|
+
handleBoldClick={handleBoldClick}
|
|
124
|
+
handleItalicClick={handleItalicClick}
|
|
125
|
+
handleLinkClick={insertLink}
|
|
126
|
+
/>
|
|
127
|
+
<Divider sx={{ backgroundColor: 'black' }} />
|
|
128
|
+
<Editable
|
|
129
|
+
style={{ minHeight: `${minRows * 20}px` }}
|
|
130
|
+
onKeyDown={onKeyDown}
|
|
131
|
+
renderElement={renderElement}
|
|
132
|
+
renderLeaf={renderLeaf}
|
|
133
|
+
/>
|
|
134
|
+
</Slate>
|
|
135
|
+
</Box>
|
|
136
|
+
</AccordionDetails>
|
|
137
|
+
</Accordion>
|
|
138
|
+
) : (
|
|
139
|
+
<>
|
|
140
|
+
{label && <Typography fontvariant="merrih4">{label}</Typography>}
|
|
141
|
+
<Box
|
|
142
|
+
sx={{
|
|
143
|
+
border: '1px solid black',
|
|
144
|
+
borderRadius: '8px',
|
|
145
|
+
width: 'auto',
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<Slate
|
|
149
|
+
editor={editor}
|
|
150
|
+
initialValue={internalValue}
|
|
151
|
+
onChange={handleChange}
|
|
152
|
+
>
|
|
153
|
+
<Toolbar
|
|
154
|
+
editor={editor}
|
|
155
|
+
markdownMode={markdownMode}
|
|
156
|
+
setMarkdown={setMarkdown}
|
|
157
|
+
handleBoldClick={handleBoldClick}
|
|
158
|
+
handleItalicClick={handleItalicClick}
|
|
159
|
+
handleLinkClick={insertLink}
|
|
160
|
+
/>
|
|
161
|
+
<Divider sx={{ backgroundColor: 'black' }} />
|
|
162
|
+
<Editable
|
|
163
|
+
style={{ minHeight: `${minRows * 20}px` }}
|
|
164
|
+
onKeyDown={onKeyDown}
|
|
165
|
+
renderElement={renderElement}
|
|
166
|
+
renderLeaf={renderLeaf}
|
|
167
|
+
/>
|
|
168
|
+
</Slate>
|
|
169
|
+
</Box>
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
</Box>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const Element = ({ attributes, children, element }: RenderElementProps) => {
|
|
177
|
+
const customElement = element as RichTextEditorTypes['CustomElement']
|
|
178
|
+
if (!customElement.type) return null
|
|
179
|
+
const style = { textAlign: customElement.align }
|
|
180
|
+
switch (customElement.type) {
|
|
181
|
+
case 'list-item':
|
|
182
|
+
return (
|
|
183
|
+
<li style={style} {...attributes}>
|
|
184
|
+
{children}
|
|
185
|
+
</li>
|
|
186
|
+
)
|
|
187
|
+
case 'link':
|
|
188
|
+
return (
|
|
189
|
+
<a href={customElement.url} {...attributes}>
|
|
190
|
+
{children}
|
|
191
|
+
</a>
|
|
192
|
+
)
|
|
193
|
+
case 'bulleted-list':
|
|
194
|
+
return (
|
|
195
|
+
<ul style={style} {...attributes}>
|
|
196
|
+
{children}
|
|
197
|
+
</ul>
|
|
198
|
+
)
|
|
199
|
+
case 'numbered-list':
|
|
200
|
+
return (
|
|
201
|
+
<ol style={style} {...attributes}>
|
|
202
|
+
{children}
|
|
203
|
+
</ol>
|
|
204
|
+
)
|
|
205
|
+
default:
|
|
206
|
+
return (
|
|
207
|
+
<p style={style} {...attributes}>
|
|
208
|
+
{children}
|
|
209
|
+
</p>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default RichTextEditor
|