Skip to content

Commit

Permalink
feat: [FX-4053] move rich text editor to a separate package (#3652)
Browse files Browse the repository at this point in the history
* feat: move rich text editor to a separate package

* chore: update yarn.lock

* chore: get back picasso RTE

* chore: update pathnames

* chore: revert back cypress tests

* chore: fix

* chore: add readme
  • Loading branch information
dmaklygin authored Jun 26, 2023
1 parent 8639927 commit 6b3b411
Show file tree
Hide file tree
Showing 134 changed files with 5,728 additions and 20 deletions.
7 changes: 7 additions & 0 deletions .changeset/tender-poems-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@toptal/picasso': patch
---

### RichTextEditor

- export utils for Typography
7 changes: 7 additions & 0 deletions .changeset/tender-poems-editor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@toptal/picasso-rich-text-editor': patch
---

### RichTextEditor

- create `Rich Text Editor` package.
6 changes: 6 additions & 0 deletions .storybook/components/CodeExample/CodeExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const imports: Record<string, object> = {
'@toptal/picasso-provider': require('@toptal/picasso-provider'),
'@toptal/picasso-pictograms': require('@toptal/picasso-pictograms'),
'@toptal/picasso-pictograms/Pictogram': require('@toptal/picasso-pictograms/Pictogram'),
'@toptal/picasso-rich-text-editor': require('@toptal/picasso-rich-text-editor'),
'@toptal/picasso-rich-text-editor/utils': require('@toptal/picasso-rich-text-editor/utils'),
}

const resolver = (path: string) => imports[path]
Expand Down Expand Up @@ -139,6 +141,10 @@ const getOriginalSourceCode = ({
return requireContext(`./picasso-pictograms/src/${src}`).default
} catch {}

try {
return requireContext(`./picasso-rich-text-editor/src/${src}`).default
} catch {}

return require(`!raw-loader!~/.storybook/stories/${src}`).default
}

Expand Down
8 changes: 8 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ module.exports = {
__dirname,
'../packages/picasso-pictograms/src'
),
'@toptal/picasso-rich-text-editor': path.resolve(
__dirname,
'../packages/picasso-rich-text-editor/src'
),
'@toptal/picasso-rich-text-editor/utils': path.resolve(
__dirname,
'../packages/picasso-rich-text-editor/src/utils'
),
},
},
}
Expand Down
Empty file.
23 changes: 23 additions & 0 deletions packages/picasso-rich-text-editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# @toptal/picasso-rich-text-editor

[![Picasso NPM package](https://img.shields.io/npm/v/@toptal/picasso-rich-text-editor?color=green&logo=toptal)](https://www.npmjs.com/package/@toptal/picasso-rich-text-editor)

`RichTextEditor` is an extensible text editor built on top of the Picasso components. It provides an intuitive user experience for rich text editing, leveraging the power and flexibility of Picasso's design system.

## Prerequisites

The following peer dependencies are required:

- `@toptal/picasso`
- `@toptal/picasso-shared`
- `@material-ui/core`
- `react`
- `react-dom`

## Setup

- `yarn add @toptal/picasso-rich-text-editor`

## Documentation

Documentation and demos are available at [picasso.toptal.net](https://picasso.toptal.net/).
53 changes: 53 additions & 0 deletions packages/picasso-rich-text-editor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@toptal/picasso-rich-text-editor",
"version": "1.0.0",
"description": "Picasso rich text editor",
"author": "Toptal",
"homepage": "https://github.com/toptal/picasso/tree/master/packages/picasso-rich-text-editor#readme",
"license": "MIT",
"main": "index.js",
"module": "index.js",
"publishConfig": {
"access": "public",
"directory": "dist-package"
},
"repository": {
"type": "git",
"url": "git+https://github.com/toptal/picasso.git"
},
"scripts": {
"build:package": "cross-env NODE_ENV=production node ../../bin/build.js --tsConfig=./tsconfig.build.json",
"prepublishOnly": "if [ -d dist-package ]; then cp ./package.json ./dist-package/package.json; fi"
},
"bugs": {
"url": "https://github.com/toptal/picasso/issues"
},
"peerDependencies": {
"@toptal/picasso": "^35.2.2",
"@toptal/picasso-shared": "^12.0.0",
"@material-ui/core": "4.12.4",
"react": ">=16.12.0 < 19.0.0",
"react-dom": ">=16.12.0 < 19.0.0",
"typescript": "~4.7.0"
},
"dependencies": {
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"classnames": "^2.3.1",
"hast-to-hyperscript": "^9.0.1",
"hast-util-from-dom": "^3.0.0",
"hast-util-sanitize": "^3.0.2",
"hast-util-to-html": "^7.1.3",
"quill": "^1.3.7",
"quill-emoji": "^0.2.0",
"quill-paste-smart": "^1.4.9",
"emoji-mart": "^5.5.2"
},
"devDependencies": {
"@testing-library/react-hooks": "^8.0.1",
"@types/classnames": "^2.3.1",
"storybook-readme": "^5.0.9",
"@material-ui/core": "4.12.4"
},
"sideEffects": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { forwardRef, lazy, Suspense } from 'react'

import QuillEditorView from '../QuillEditorView'
import type { Props } from './QuillEditor'

const QuillEditor = lazy(() => import('./QuillEditor'))

const LazyQuillEditor = forwardRef<HTMLDivElement, Props>(
function LazyQuillEditor(props, ref) {
return (
<Suspense fallback={<QuillEditorView />}>
<QuillEditor {...props} ref={ref} />
</Suspense>
)
}
)

export default LazyQuillEditor
94 changes: 94 additions & 0 deletions packages/picasso-rich-text-editor/src/QuillEditor/QuillEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { forwardRef, useRef } from 'react'
import type { BaseProps } from '@toptal/picasso-shared'
import { useCombinedRefs } from '@toptal/picasso/utils'

import QuillEditorView from '../QuillEditorView'
import useQuillInstance from './hooks/useQuillInstance'
import {
useFocus,
useSubscribeToQuillEvents,
useDisabledEditor,
useKeyBindings,
useSubscribeToTextEditorEvents,
} from './hooks'
import useDefaultValue from './hooks/useDefaultValue'
import type {
TextFormatHandler,
ChangeHandler,
SelectionHandler,
TextLengthChangeHandler,
EditorPlugin,
} from './types'

export type Props = BaseProps & {
/**
* HTML string
*/
defaultValue?: string
disabled: boolean
id: string
isFocused: boolean
placeholder?: string
plugins?: EditorPlugin[]
onSelectionChange: SelectionHandler
onTextFormat: TextFormatHandler
onTextChange: ChangeHandler
onTextLengthChange: TextLengthChangeHandler
}

const QuillEditor = forwardRef<HTMLDivElement, Props>(function QuillEditor(
{
defaultValue,
disabled,
'data-testid': dataTestId,
id,
isFocused,
placeholder,
onTextLengthChange,
onSelectionChange,
onTextFormat,
onTextChange,
plugins,
},
ref
) {
const quill = useQuillInstance({
id,
placeholder,
plugins,
})
const editorRef = useCombinedRefs<HTMLDivElement>(
ref,
useRef<HTMLDivElement>(null)
)

useFocus({ isFocused, quill })
useKeyBindings({ quill, onTextFormat })
useSubscribeToQuillEvents({
quill,
onTextChange,
onSelectionChange,
onTextLengthChange,
})
useSubscribeToTextEditorEvents({
editorRef,
quill,
})
useDefaultValue({ defaultValue, quill })
useDisabledEditor({ disabled, quill })

return <QuillEditorView ref={editorRef} data-testid={dataTestId} id={id} />
})

QuillEditor.defaultProps = {
disabled: false,
isFocused: false,
onSelectionChange: () => {},
onTextFormat: () => {},
onTextLengthChange: () => {},
onTextChange: () => {},
}

QuillEditor.displayName = 'QuillEditor'

export default QuillEditor
27 changes: 27 additions & 0 deletions packages/picasso-rich-text-editor/src/QuillEditor/blots/emoji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Quill from 'quill'

const EmbedBlot = Quill.import('blots/embed')

export class EmojiBlot extends EmbedBlot {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static create(data: any) {
const node = super.create(data)

node.setAttribute('data-src', data.src)
node.setAttribute('data-emoji-name', data.emojiId)
node.setAttribute('src', data.src)
node.setAttribute('class', 'emoji-icon')

return node
}

static value(domNode: { dataset: { src: string } }) {
const { src } = domNode.dataset

return { src }
}
}

EmojiBlot.blotName = 'emojiBlot'
EmojiBlot.className = 'emoji-blot'
EmojiBlot.tagName = 'img'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CUSTOM_QUILL_EDITOR_FORMAT_EVENT = 'quill-editor-format'
export const INSERT_DEFAULT_LINK_TEXT = 'quill-editor-insert-default-link-text'
export const INSERT_EMOJI = 'quill-editor-insert-emoji'
24 changes: 24 additions & 0 deletions packages/picasso-rich-text-editor/src/QuillEditor/formats/bold.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Classes } from '@toptal/picasso-shared'
import Quill from 'quill'
import { getTypographyClassName } from '@toptal/picasso'

const QuillBold = Quill.import('formats/bold')

const makeBoldFormat = (typographyClasses: Classes) =>
class LinkBlot extends QuillBold {
static create(value: string) {
const node = super.create(value)

node.classList.add(
...getTypographyClassName(typographyClasses, {
variant: 'body',
size: 'inherit',
weight: 'semibold',
}).split(' ')
)

return node
}
}

export default makeBoldFormat
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Classes } from '@toptal/picasso-shared'
import Quill from 'quill'
import { getTypographyClassName } from '@toptal/picasso'

const QuillHeader = Quill.import('formats/header')

const makeHeaderFormat = (typographyClasses: Classes) =>
class LinkBlot extends QuillHeader {
static create(value: string) {
const node = super.create(value)

node.classList.add(
...getTypographyClassName(typographyClasses, {
variant: 'heading',
size: 'medium',
}).split(' ')
)

return node
}
}

export default makeHeaderFormat
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { default as makeHeaderFormat } from './header'
export { default as makeBoldFormat } from './bold'
export { default as useTypographyClasses } from './use-typography-classes'
export { default as makeLinkFormat } from './link'
30 changes: 30 additions & 0 deletions packages/picasso-rich-text-editor/src/QuillEditor/formats/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Classes } from '@toptal/picasso-shared'
import Quill from 'quill'
import { getTypographyClassName } from '@toptal/picasso'

const QuillLink = Quill.import('formats/link')

const makeLinkFormat = (typographyClasses: Classes) =>
class LinkBlot extends QuillLink {
static create(value: string) {
const node = super.create(value)

node.classList.add(
...getTypographyClassName(typographyClasses, {
variant: 'body',
size: 'inherit',
underline: 'solid',
// we don't expose blue color in typography since it should be used only for links.
// in this case we are simulating look of the link
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
color: 'blue',
weight: 'regular',
}).split(' ')
)

return node
}
}

export default makeLinkFormat
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Theme } from '@material-ui/core'
import { makeStyles } from '@material-ui/core'
import typographyStyles from '@toptal/picasso/Typography/styles'

const useTypographyClasses = makeStyles<Theme>(typographyStyles, {
name: 'TextEditorTypography',
})

export default useTypographyClasses
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default as useDisabledEditor } from './useDisabledEditor'
export { default as useFocus } from './useFocus'
export { default as useKeyBindings } from './useKeyBindings'
export { default as useQuillInstance } from './useQuillInstance'
export { default as useSubscribeToQuillEvents } from './useSubscribeToQuillEvents'
export { default as useSubscribeToTextEditorEvents } from './useSubscribeToTextEditorEvents'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './useDefaultValue'
Loading

0 comments on commit 6b3b411

Please sign in to comment.