goobs-frontend 0.8.9 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goobs-frontend",
3
- "version": "0.8.9",
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",
@@ -25,13 +25,18 @@
25
25
  "@mui/icons-material": "^6.1.10",
26
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
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
42
  "@next/eslint-plugin-next": "^15.0.4",
@@ -1,8 +1,9 @@
1
1
  'use client'
2
2
  import React from 'react'
3
- import { Button, Box, ButtonProps, SxProps, Theme } from '@mui/material'
3
+ import { Button, Box, ButtonProps } from '@mui/material'
4
4
  import Typography from '../Typography'
5
5
  import { SvgIconProps } from '@mui/material/SvgIcon'
6
+ import { styled } from '@mui/material/styles'
6
7
 
7
8
  export interface CustomButtonProps extends ButtonProps {
8
9
  text?: string
@@ -19,9 +20,73 @@ export interface CustomButtonProps extends ButtonProps {
19
20
  fontlocation?: 'left' | 'center' | 'right'
20
21
  }
21
22
 
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
+ }))
61
+
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
+ })
70
+
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
+ }))
86
+
22
87
  const CustomButton: React.FC<CustomButtonProps> = ({
23
88
  text,
24
- variant,
89
+ variant = 'contained',
25
90
  fontvariant = 'merriparagraph',
26
91
  onClick,
27
92
  fontcolor,
@@ -44,27 +109,16 @@ const CustomButton: React.FC<CustomButtonProps> = ({
44
109
  onClick?.(event)
45
110
  }
46
111
 
47
- const buttonSx: SxProps<Theme> = {
48
- ...(backgroundcolor && { backgroundColor: backgroundcolor }),
49
- ...(width && { width }),
50
- ...(height && { height }),
51
- flexDirection: iconlocation === 'above' ? 'column' : 'row',
52
- justifyContent:
53
- fontlocation === 'left'
54
- ? 'flex-start'
55
- : fontlocation === 'right'
56
- ? 'flex-end'
57
- : 'center',
58
- ...(sx as object),
59
- }
60
-
61
112
  const isDisabled = disableButton === 'true'
62
113
 
63
114
  const IconComponent = icon
64
115
  ? React.cloneElement(icon, {
65
116
  sx: {
66
- color: iconcolor,
67
- fontSize: iconsize,
117
+ color: iconcolor || 'inherit',
118
+ fontSize: iconsize || '20px',
119
+ minWidth: iconsize || '20px',
120
+ minHeight: iconsize || '20px',
121
+ margin: 0,
68
122
  },
69
123
  } as Partial<SvgIconProps>)
70
124
  : null
@@ -72,48 +126,36 @@ const CustomButton: React.FC<CustomButtonProps> = ({
72
126
  const buttonContent = (
73
127
  <>
74
128
  {iconlocation === 'above' && IconComponent}
75
- <Box
76
- display="flex"
77
- alignItems="center"
78
- justifyContent={
79
- fontlocation === 'left'
80
- ? 'flex-start'
81
- : fontlocation === 'right'
82
- ? 'flex-end'
83
- : 'center'
84
- }
85
- width="100%"
86
- height="100%"
87
- >
129
+ <ContentWrapper fontlocation={fontlocation}>
88
130
  {iconlocation === 'left' && IconComponent}
89
131
  <Typography
90
132
  fontvariant={fontvariant}
91
- fontcolor={isDisabled ? 'grey' : fontcolor}
133
+ fontcolor={isDisabled ? 'grey' : fontcolor || 'white'}
92
134
  text={text || ''}
93
135
  />
94
136
  {iconlocation === 'right' && IconComponent}
95
- </Box>
137
+ </ContentWrapper>
96
138
  </>
97
139
  )
98
140
 
99
141
  return (
100
- <Box
101
- display="flex"
102
- flexDirection="column"
103
- alignItems="center"
104
- width={width}
105
- height={height}
106
- >
107
- <Button
142
+ <StyledBox sx={{ width, height }}>
143
+ <StyledButton
108
144
  {...restProps}
109
145
  variant={variant}
110
146
  onClick={handleButtonClick}
111
- sx={buttonSx}
112
147
  disabled={isDisabled}
148
+ disableElevation
149
+ disableRipple
150
+ fullWidth
151
+ backgroundcolor={backgroundcolor}
152
+ iconlocation={iconlocation}
153
+ fontlocation={fontlocation}
154
+ sx={sx}
113
155
  >
114
156
  {buttonContent}
115
- </Button>
116
- </Box>
157
+ </StyledButton>
158
+ </StyledBox>
117
159
  )
118
160
  }
119
161
 
@@ -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
@@ -0,0 +1,65 @@
1
+ import React from 'react'
2
+ import { Box, TextField } from '@mui/material'
3
+
4
+ type SimpleEditorProps = {
5
+ value: string
6
+ setValue: (value: string) => void
7
+ minRows?: number
8
+ label?: string
9
+ }
10
+
11
+ const SimpleEditor: React.FC<SimpleEditorProps> = ({
12
+ value,
13
+ setValue,
14
+ minRows = 5,
15
+ label,
16
+ }) => {
17
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
18
+ setValue(event.target.value)
19
+ }
20
+
21
+ return (
22
+ <Box
23
+ sx={{
24
+ width: '100%',
25
+ display: 'flex',
26
+ flexDirection: 'column',
27
+ }}
28
+ >
29
+ <TextField
30
+ fullWidth
31
+ multiline
32
+ label={label}
33
+ variant="outlined"
34
+ minRows={minRows}
35
+ value={value}
36
+ onChange={handleChange}
37
+ sx={{
38
+ '& .MuiOutlinedInput-root': {
39
+ borderRadius: '8px',
40
+ '& fieldset': {
41
+ borderColor: 'black',
42
+ },
43
+ '&:hover fieldset': {
44
+ borderColor: 'black',
45
+ },
46
+ '&.Mui-focused fieldset': {
47
+ borderColor: 'black',
48
+ },
49
+ '& .MuiInputBase-input': {
50
+ transform: 'translateY(-8px)',
51
+ },
52
+ },
53
+ '& .MuiInputLabel-root': {
54
+ color: 'black',
55
+ '&.Mui-focused': {
56
+ color: 'black',
57
+ },
58
+ },
59
+ }}
60
+ />
61
+ </Box>
62
+ )
63
+ }
64
+
65
+ export default SimpleEditor
@@ -0,0 +1,111 @@
1
+ import React from 'react'
2
+ import { BaseEditor } from 'slate'
3
+ import { ReactEditor } from 'slate-react'
4
+ import { HistoryEditor } from 'slate-history'
5
+ import { useRichTextEditor } from '../utils/useRichtextEditor'
6
+ import { handleBoldClick, handleItalicClick } from '../utils/useMarkdownEditor'
7
+ import { IconButton, IconButtonProps } from '@mui/material'
8
+ import { InlineFormat, BlockFormat, AlignmentFormat } from '../types'
9
+
10
+ interface ToolbarButtonProps extends IconButtonProps {
11
+ format?: InlineFormat | BlockFormat | AlignmentFormat
12
+ children: React.ReactNode
13
+ editor?: BaseEditor & ReactEditor & HistoryEditor
14
+ buttonAction?: 'undo' | 'redo'
15
+ markdownMode?: boolean
16
+ setMarkdown?: (value: string) => void
17
+ handleMouseDown?: () => void
18
+ activeFontColor?: string
19
+ activeBackgroundColor?: string
20
+ }
21
+
22
+ const ToolbarButton: React.FC<ToolbarButtonProps> = ({
23
+ format,
24
+ children,
25
+ editor,
26
+ buttonAction,
27
+ markdownMode,
28
+ setMarkdown,
29
+ activeFontColor = 'rgb(110 110 110)',
30
+ activeBackgroundColor = 'rgba(0, 0, 0, 0.10)',
31
+ ...props
32
+ }) => {
33
+ const { toggleMark, toggleBlock, isMarkActive, isBlockActive } =
34
+ useRichTextEditor([], () => {})
35
+
36
+ const isActive =
37
+ editor &&
38
+ format &&
39
+ (isMarkActive(format as InlineFormat) ||
40
+ isBlockActive(format as BlockFormat | AlignmentFormat))
41
+
42
+ const handleMouseDown = () => {
43
+ if (markdownMode && setMarkdown) {
44
+ const selection = window.getSelection()
45
+ if (selection) {
46
+ const selectedText = selection.toString()
47
+ switch (format) {
48
+ case 'bold':
49
+ handleBoldClick(selectedText, selectedText, setMarkdown)
50
+ break
51
+ case 'italic':
52
+ handleItalicClick(selectedText, selectedText, setMarkdown)
53
+ break
54
+ case 'code':
55
+ setMarkdown('`' + selectedText + '`')
56
+ break
57
+ case 'link': {
58
+ const url = prompt('Enter the URL')
59
+ if (url) {
60
+ setMarkdown('[' + selectedText + '](' + url + ')')
61
+ }
62
+ break
63
+ }
64
+ }
65
+ }
66
+ } else {
67
+ // Slate mode
68
+ if (editor) {
69
+ if (buttonAction) {
70
+ if (buttonAction === 'undo') {
71
+ editor.undo()
72
+ } else if (buttonAction === 'redo') {
73
+ editor.redo()
74
+ }
75
+ } else if (format) {
76
+ if (
77
+ format === 'bulleted-list' ||
78
+ format === 'numbered-list' ||
79
+ ['left', 'center', 'right', 'justify'].includes(format as string)
80
+ ) {
81
+ toggleBlock(format as BlockFormat | AlignmentFormat)
82
+ } else {
83
+ toggleMark(format as InlineFormat)
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ return (
91
+ <IconButton
92
+ onMouseDown={handleMouseDown}
93
+ sx={{
94
+ borderRadius: '9%',
95
+ color: 'black',
96
+ ...(isActive && {
97
+ color: activeFontColor,
98
+ backgroundColor: activeBackgroundColor,
99
+ }),
100
+ '& .MuiTouchRipple-root .MuiTouchRipple-child': {
101
+ borderRadius: '9%',
102
+ },
103
+ }}
104
+ {...props}
105
+ >
106
+ {children}
107
+ </IconButton>
108
+ )
109
+ }
110
+
111
+ export default ToolbarButton