Skip to content

Commit

Permalink
📝 Markdown editor (#4681)
Browse files Browse the repository at this point in the history
* Add markdown editor

* Parse content from and to markdown

* Render markdown

* WIP

* Add format buttons

* Flatten text nodes

* Style the toolbar

* Switch to pure markdown

* Add basic markdown "shortcuts"

* Improve inline formats wrapping

* Add link and separator buttons

* Remove unused dependencies

* Support undo/redo via keyboard shortcuts

* Add a placeholder

* Add a max length

* Small refactor

* Clean unused code

* Pre-select the url after adding a link

* Add common keyboard shortcuts

* Re-use Atlas styles
  • Loading branch information
thesan authored Aug 23, 2023
1 parent 059fec0 commit 9b06879
Show file tree
Hide file tree
Showing 24 changed files with 823 additions and 38 deletions.
3 changes: 3 additions & 0 deletions packages/atlas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
"react-transition-group": "^4.4.5",
"retry-axios": "^3.0.0",
"scroll-lock": "^2.1.5",
"slate": "^0.94.1",
"slate-history": "^0.93.0",
"slate-react": "^0.98.1",
"subscriptions-transport-ws": "^0.11.0",
"swiper": "^9.1.1",
"twemoji": "^14.0.2",
Expand Down
11 changes: 11 additions & 0 deletions packages/atlas/src/assets/icons/Blockquote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
import { Ref, SVGProps, forwardRef, memo } from 'react'

const SvgBlockquote = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
<path d="M1 14h2V2H1v12ZM13 4V2H6v2h7ZM15 7v2H6V7h9ZM13 14v-2H6v2h7Z" fill="#F4F6F8" />
</svg>
))
SvgBlockquote.displayName = 'SvgBlockquote'
const Memo = memo(SvgBlockquote)
export { Memo as SvgBlockquote }
16 changes: 16 additions & 0 deletions packages/atlas/src/assets/icons/Bold.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
import { Ref, SVGProps, forwardRef, memo } from 'react'

const SvgBold = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3 2h5a3.5 3.5 0 0 1 2.984 5.33A3.5 3.5 0 0 1 9.5 14H3V2Zm2 10h4.5a1.5 1.5 0 0 0 0-3H5v3Zm0-5h3a1.5 1.5 0 1 0 0-3H5v3Z"
fill="#F4F6F8"
/>
</svg>
))
SvgBold.displayName = 'SvgBold'
const Memo = memo(SvgBold)
export { Memo as SvgBold }
11 changes: 11 additions & 0 deletions packages/atlas/src/assets/icons/Heading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
import { Ref, SVGProps, forwardRef, memo } from 'react'

const SvgHeading = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
<path d="M5 2H3v12h2V9h6v5h2V2h-2v5H5V2Z" fill="#F4F6F8" />
</svg>
))
SvgHeading.displayName = 'SvgHeading'
const Memo = memo(SvgHeading)
export { Memo as SvgHeading }
11 changes: 11 additions & 0 deletions packages/atlas/src/assets/icons/Italic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
import { Ref, SVGProps, forwardRef, memo } from 'react'

const SvgItalic = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
<path d="M13 2v2H9.82l-1.6 8H11v2H3v-2h3.18l1.6-8H5V2h8Z" fill="#F4F6F8" />
</svg>
))
SvgItalic.displayName = 'SvgItalic'
const Memo = memo(SvgItalic)
export { Memo as SvgItalic }
14 changes: 14 additions & 0 deletions packages/atlas/src/assets/icons/OrderedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
import { Ref, SVGProps, forwardRef, memo } from 'react'

const SvgOrderedList = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
<path
d="M15 4V2H6v2h9ZM2 3.707l-.646.647-.708-.708L2.13 2.163A.51.51 0 0 1 3 2.524V6h1v1H1V6h1V3.707ZM1.444 9.48a1.4 1.4 0 0 1 2.212 1.716L2.363 13H4v1H1.392a.501.501 0 0 1-.407-.793l1.858-2.593a.4.4 0 0 0-.633-.491l-.437.52-.766-.642.437-.52ZM15 7v2H6V7h9ZM15 14v-2H6v2h9Z"
fill="#F4F6F8"
/>
</svg>
))
SvgOrderedList.displayName = 'SvgOrderedList'
const Memo = memo(SvgOrderedList)
export { Memo as SvgOrderedList }
14 changes: 14 additions & 0 deletions packages/atlas/src/assets/icons/Strikethrough.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
import { Ref, SVGProps, forwardRef, memo } from 'react'

const SvgStrikethrough = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
<path
d="M8.455 3H7.364A2.364 2.364 0 0 0 5 5.364C5 6.267 5.733 7 6.636 7H14v1H2V7h1.388A3.621 3.621 0 0 1 3 5.364 4.364 4.364 0 0 1 7.364 1h1.09a4.546 4.546 0 0 1 4.514 4H10.94a2.546 2.546 0 0 0-2.486-2ZM8.636 13h-1.09a2.546 2.546 0 0 1-2.488-2H3.032c.27 2.253 2.188 4 4.513 4h1.091A4.364 4.364 0 0 0 13 10.636 3.62 3.62 0 0 0 12.612 9H9.363C10.267 9 11 9.733 11 10.636A2.364 2.364 0 0 1 8.636 13Z"
fill="#F4F6F8"
/>
</svg>
))
SvgStrikethrough.displayName = 'SvgStrikethrough'
const Memo = memo(SvgStrikethrough)
export { Memo as SvgStrikethrough }
14 changes: 14 additions & 0 deletions packages/atlas/src/assets/icons/UnorderedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// THIS FILE WAS AUTOGENERATED BY SVGR. DO NOT MODIFY IT MANUALLY;
import { Ref, SVGProps, forwardRef, memo } from 'react'

const SvgUnorderedList = forwardRef((props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg width={16} height={16} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" ref={ref} {...props}>
<path
d="M2 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM5 4h10V2H5v2ZM15 9V7H5v2h10ZM5 12h10v2H5v-2ZM3 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM2 14a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
fill="#F4F6F8"
/>
</svg>
))
SvgUnorderedList.displayName = 'SvgUnorderedList'
const Memo = memo(SvgUnorderedList)
export { Memo as SvgUnorderedList }
8 changes: 7 additions & 1 deletion packages/atlas/src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export * from './ActionNewTab'
export * from './ActionNft'
export * from './ActionNotForSale'
export * from './ActionNotifications'
export * from './ActionOrderedList'
export * from './ActionPan'
export * from './ActionPause'
export * from './ActionPayment'
Expand Down Expand Up @@ -116,6 +115,8 @@ export * from './AlertsSuccess24'
export * from './AlertsSuccess32'
export * from './AlertsWarning24'
export * from './AlertsWarning32'
export * from './Blockquote'
export * from './Bold'
export * from './CategoriesAutosAndVehicles'
export * from './CategoriesComedy'
export * from './CategoriesEducation'
Expand Down Expand Up @@ -177,6 +178,7 @@ export * from './ControlsVideoModeCompactView'
export * from './ControlsVideo'
export * from './ControlsZoomIn'
export * from './ControlsZoomOut'
export * from './Heading'
export * from './IllustrativeEdit'
export * from './IllustrativeFileFailed'
export * from './IllustrativeFileSelected'
Expand All @@ -185,6 +187,7 @@ export * from './IllustrativePlay'
export * from './IllustrativeReupload'
export * from './IllustrativeTrash'
export * from './IllustrativeVideo'
export * from './Italic'
export * from './JoyTokenMonochrome16'
export * from './JoyTokenMonochrome24'
export * from './JoyTokenMonochrome32'
Expand Down Expand Up @@ -241,6 +244,7 @@ export * from './LogoWhatsAppOnDark'
export * from './LogoWhatsAppOnLight'
export * from './LogoYoutubeWhiteFull'
export * from './LogoYoutube'
export * from './OrderedList'
export * from './SidebarChannel'
export * from './SidebarChannels'
export * from './SidebarExplore'
Expand All @@ -259,6 +263,8 @@ export * from './SidebarToken'
export * from './SidebarUpload'
export * from './SidebarVideos'
export * from './SidebarYpp'
export * from './Strikethrough'
export * from './TierIcon1'
export * from './TierIcon2'
export * from './TierIcon3'
export * from './UnorderedList'
6 changes: 6 additions & 0 deletions packages/atlas/src/assets/icons/svgs/blockquote.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/atlas/src/assets/icons/svgs/bold.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/atlas/src/assets/icons/svgs/heading.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/atlas/src/assets/icons/svgs/italic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/atlas/src/assets/icons/svgs/strikethrough.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions packages/atlas/src/assets/icons/svgs/unordered-list.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 18 additions & 25 deletions packages/atlas/src/components/_buttons/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,28 @@
import { To } from 'history'
import { AnimationEvent, ElementType, KeyboardEvent, MouseEvent, PropsWithChildren, ReactNode, forwardRef } from 'react'
import { ButtonHTMLAttributes, ElementType, PropsWithChildren, ReactNode, forwardRef } from 'react'

import { Text, TextVariant } from '@/components/Text'
import { getLinkPropsFromTo } from '@/utils/button'

import { BorderWrapper, ButtonBase, ButtonIconWrapper, ButtonSize, ButtonVariant, IconPlacement } from './Button.styles'

export type ButtonProps = PropsWithChildren<{
as?: ElementType
icon?: ReactNode
iconPlacement?: IconPlacement
badge?: boolean | string | number
fullWidth?: boolean
size?: ButtonSize
to?: To
openLinkInNewTab?: boolean
type?: 'button' | 'submit'
variant?: ButtonVariant
disabled?: boolean
tabIndex?: number
className?: string
onClick?: (e: MouseEvent<HTMLButtonElement>) => void
onMouseEnter?: (e: MouseEvent<HTMLButtonElement>) => void
onMouseLeave?: (e: MouseEvent<HTMLButtonElement>) => void
onAnimationEnd?: (e: AnimationEvent<HTMLButtonElement>) => void
onKeyPress?: (e: KeyboardEvent<HTMLButtonElement>) => void
rounded?: boolean
// internal
_textOnly?: boolean
ariaLabel?: string
}>
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
PropsWithChildren<{
as?: ElementType
icon?: ReactNode
iconPlacement?: IconPlacement
badge?: boolean | string | number
fullWidth?: boolean
size?: ButtonSize
to?: To
openLinkInNewTab?: boolean
type?: 'button' | 'submit'
variant?: ButtonVariant
rounded?: boolean
// internal
_textOnly?: boolean
ariaLabel?: string
}>

const BUTTON_SIZE_TO_TEXT_VARIANT: Record<ButtonSize, TextVariant> = {
large: 't300-strong',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Meta, StoryObj } from '@storybook/react'

import { MarkdownEditorProps, MarkdownEditor as MarkdownEditor_ } from './MarkdownEditor'

export default {
title: 'inputs/MarkdownEditor',
component: MarkdownEditor_,

args: {
value: '',
placeholder: 'Placeholder',
maxLength: 30,
},
} as Meta

export const MarkdownEditor: StoryObj<MarkdownEditorProps> = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styled from '@emotion/styled'
import { Editable } from 'slate-react'

import { cVar, sizes } from '@/styles'

import { TextAreaStyles } from '../TextArea/TextArea.styles'

export { CustomBorder, TextAreaContainer as EditorAreaContainer } from '../TextArea/TextArea.styles'

export const EditorWrapper = styled.div`
display: inline-block;
width: 100%;
`

export const StyledEditable = styled(Editable)<{ error?: boolean }>`
${TextAreaStyles}
margin-bottom: ${sizes(2)};
min-height: 128px !important;
`

export const ToolBar = styled.div`
overflow-x: auto;
background-color: ${cVar('colorBackgroundMutedAlpha')};
display: flex;
padding: ${sizes(2)};
gap: ${sizes(2)};
`
Loading

0 comments on commit 9b06879

Please sign in to comment.