Skip to content

Commit

Permalink
feature: accordion
Browse files Browse the repository at this point in the history
  • Loading branch information
hassanad94 committed Jun 5, 2024
1 parent 54b46a9 commit a33c5d9
Show file tree
Hide file tree
Showing 19 changed files with 819 additions and 8 deletions.
250 changes: 250 additions & 0 deletions packages/sn-editor-react/src/components/controls/accordion-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import {
Button,
createStyles,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
IconButtonProps,
makeStyles,
TextField,
Theme,
Tooltip,
useTheme,
} from '@material-ui/core'
import { ArrowDropDown, Close, DragHandle } from '@material-ui/icons'

import { Editor } from '@tiptap/react'
import React, { FC, useCallback, useRef, useState } from 'react'
import { renderToString } from 'react-dom/server'
import { useLocalization } from '../../hooks'

const useStyles = makeStyles((theme: Theme) => {
return createStyles({
ListIcon: {
paddingLeft: '0px',
'& .down-arrow': {
position: 'absolute',
right: '0',
marginRight: '-6px',
top: '0',
marginTop: '3px',
},
},
accordion: {
display: 'flex',
flexDirection: 'column',
rowGap: '15px',
border: '2px solid',
borderColor: theme.palette.primary.main,
position: 'relative',
padding: '10px',
borderRadius: '10px',
'& .panel-close-button': {
position: 'absolute',
right: '0',
top: '0',
},
},
accordionContainer: {
display: 'flex',
flexDirection: 'column',
rowGap: '20px',
},
actionPanel: {
display: 'flex',
flex: 1,
justifyContent: 'space-between',
},
dialog: {
'& .MuiDialogContent-root': {
padding: '8px 15px',
},
},
})
})

interface AccordionControlProps {
editor: Editor
buttonProps?: Partial<IconButtonProps>
}

type TAccordions = {
title: string
body: string
}

type PanelPros = {
title: string
body: string
}

const Panel = ({ title, body }: PanelPros) => {
return (
<div className="panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">
<a href="#collapse1716279617835-0" className="collapsed">
<i className="svg" />
{title}
</a>
</h3>
</div>
<div className="panel-collapse collapse">
<div className="panel-body">{body}</div>
</div>
</div>
)
}

const initialAccordion = { title: '', body: '' }

export const AccordionControl: FC<AccordionControlProps> = ({ buttonProps, editor }) => {
const [open, setOpen] = useState(false)

const [accordions, setAccordions] = useState<TAccordions[]>([initialAccordion])
const form = useRef<HTMLFormElement>(null)

const theme = useTheme()

const classes = useStyles(theme)
const localization = useLocalization()

const handleClickOpen = () => {
setOpen(true)
}

const handleClose = useCallback(() => {
setAccordions([initialAccordion])

setOpen(false)
}, [])

const handleSubmit = () => {
if (accordions.length === 0) {
handleClose()
return
}

if (!form.current?.reportValidity()) {
return
}

const panelGroup = renderToString(
<>
<div className="panel-group">
{accordions.map((item, index) => {
return <Panel key={index} title={item.title} body={item.body} />
})}
</div>
{/* eslint-disable-next-line react/self-closing-comp*/}
<p></p>
</>,
)

editor.chain().focus().insertContent(panelGroup).run()

handleClose()
}

const handleClosePanel = (index: number) => {
setAccordions((prev) => {
return prev.filter((_, i) => i !== index)
})
}

return (
<>
<Tooltip title={localization.accordionControl.title}>
<IconButton className={classes.ListIcon} onClick={handleClickOpen} {...buttonProps}>
<ArrowDropDown className="down-arrow" />
<DragHandle />
</IconButton>
</Tooltip>
<Dialog
open={open}
className={classes.dialog}
onClose={handleSubmit}
aria-labelledby="form-dialog-title"
maxWidth="md"
fullWidth>
<DialogTitle id="form-dialog-title">{localization.accordionControl.title}</DialogTitle>
<DialogContent>
<form ref={form} className={classes.accordionContainer}>
{accordions.map((accordion, index) => {
return (
<div key={index} className={classes.accordion}>
<Tooltip title={localization.accordionControl.closePanel}>
<IconButton className="panel-close-button" onClick={() => handleClosePanel(index)} {...buttonProps}>
<Close color="error" />
</IconButton>
</Tooltip>

<TextField
autoFocus
margin="dense"
label={localization.accordionControl.title}
type="text"
required
fullWidth
value={accordion.title}
onChange={(e) => {
const newAccordions = [...accordions]

newAccordions[index] = { ...accordions[index], title: e.target.value }

setAccordions(newAccordions)
}}
/>

<TextField
multiline
autoFocus
margin="dense"
label={localization.accordionControl.body}
type="text"
required
fullWidth
value={accordion.body}
onChange={(e) => {
const newAccordions = [...accordions]

newAccordions[index] = { ...accordions[index], body: e.target.value }

setAccordions(newAccordions)
}}
/>
</div>
)
})}
</form>
</DialogContent>
<DialogActions>
<div className={classes.actionPanel}>
<Button
onClick={() =>
setAccordions((prev) => {
return [...prev, initialAccordion]
})
}
title={localization.accordionControl.addPanel}>
{localization.accordionControl.addPanel}
</Button>

<div className="close-actions">
<Button onClick={handleClose}>{localization.common.cancel}</Button>
<Button
onClick={() => {
handleSubmit()
}}
color="primary">
{localization.imageControl.submit}
</Button>
</div>
</div>
</DialogActions>
</Dialog>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export const HTMLEditorControl: FC<HTMLEditorControlProps> = ({ editor, buttonPr
setOpen(true)
}

const saveHTMLContent = () => {
editor.chain().focus().setContent(html, true).run()
setOpen(false)
}

const handleClose = () => {
if (editor.getHTML() === html) {
setOpen(false)
Expand All @@ -38,9 +43,7 @@ export const HTMLEditorControl: FC<HTMLEditorControlProps> = ({ editor, buttonPr
if (!confirmResult) {
return
}
editor.commands.setContent(html)

setOpen(false)
saveHTMLContent()
}

return (
Expand All @@ -60,7 +63,7 @@ export const HTMLEditorControl: FC<HTMLEditorControlProps> = ({ editor, buttonPr
aria-labelledby="html-editor-control-title"
fullWidth
maxWidth="lg"
onExited={() => { }}>
onExited={() => {}}>
<DialogTitle id="html-editor-control-title">{localization.HTMLEditorControl.title}</DialogTitle>
<DialogContent>
<HtmlEditor initialState={editor.getHTML()} fieldOnChange={setHtml} />
Expand All @@ -74,8 +77,7 @@ export const HTMLEditorControl: FC<HTMLEditorControlProps> = ({ editor, buttonPr
</Button>
<Button
onClick={() => {
editor.commands.setContent(html)
setOpen(false)
saveHTMLContent()
}}
color="primary">
{localization.linkControl.submit}
Expand Down
1 change: 1 addition & 0 deletions packages/sn-editor-react/src/components/controls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './link-control'
export * from './table-control'
export * from './typography-control'
export * from './html-editor-control'
export * from './accordion-control'
49 changes: 49 additions & 0 deletions packages/sn-editor-react/src/components/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,55 @@ const useStyles = makeStyles((theme) => {
borderRadius: commonStyles.editorBorderRadius,
border: theme.palette.type === 'dark' ? commonStyles.editorBorder : 'unset',

'& .panel-group': {
paddingBlock: '10px',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
rowGap: '20px',
'& .panel': {
display: 'inline-block',
border: '2px solid',
borderColor: theme.palette.primary.main,
borderRadius: '8px',
position: 'relative',
maxWidth: '100%',
paddingBlock: '5px',
'& .panel-heading': {
borderBottom: '1px solid',
borderBottomColor: theme.palette.primary.main,
'& h3': {
margin: '0px',
padding: '0px 30px 5px 10px',
display: 'block',
'& a ': {
textDecoration: 'none',
color: 'currentColor',
letterSpacing: '1px',
fontWeight: '500',
},
},
},
'& .panel-body': {
paddingInline: '10px',
paddingTop: '10px',
'& .panel-collapse': {
paddingRight: '10px',
},
},
'&:after': {
content: '"➧"',
position: 'absolute',
top: '0',
right: '0',
transform: 'rotate(90deg)',
marginTop: '-1px',
fontSize: '26px',
marginRight: '5px',
},
},
},

'& .ProseMirror': {
outline: 0,

Expand Down
23 changes: 22 additions & 1 deletion packages/sn-editor-react/src/components/menu-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import { Editor } from '@tiptap/react'
import React, { FC } from 'react'
import { useLocalization } from '../hooks'
import { getCommonStyles } from '../styles'
import { HTMLEditorControl, ImageControl, LinkControl, TableControl, TypographyControl } from './controls'
import {
AccordionControl,
HTMLEditorControl,
ImageControl,
LinkControl,
TableControl,
TypographyControl,
} from './controls'

const useStyles = makeStyles((theme) => {
const commonStyles = getCommonStyles(theme)
Expand Down Expand Up @@ -70,6 +77,16 @@ export const MenuBar: FC<MenuBarProps> = ({ editor }) => {
<div className={classes.root}>
<TypographyControl editor={editor} />
<div className={classes.divider} />
{/* <Tooltip title={`${localization.menubar.bold}`}>
<IconButton
onClick={() =>
editor.chain().focus().setYoutubeVideo({ src: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' }).run()
}
color={editor.isActive('bold') ? 'primary' : 'default'}
classes={{ root: classes.button, colorPrimary: classes.buttonPrimary }}>
<strong>B</strong>
</IconButton>
</Tooltip> */}
<Tooltip title={`${localization.menubar.bold} (Ctrl + B)`}>
<IconButton
onClick={() => editor.chain().focus().toggleBold().run()}
Expand Down Expand Up @@ -201,6 +218,10 @@ export const MenuBar: FC<MenuBarProps> = ({ editor }) => {
<RedoIcon />
</IconButton>
</Tooltip>
<AccordionControl
editor={editor}
buttonProps={{ classes: { root: classes.button, colorPrimary: classes.buttonPrimary } }}
/>
<HTMLEditorControl
editor={editor}
buttonProps={{ classes: { root: classes.button, colorPrimary: classes.buttonPrimary } }}
Expand Down
Loading

0 comments on commit a33c5d9

Please sign in to comment.