Adding emoji picker
This commit is contained in:
parent
8afac6781d
commit
ec93778293
12 changed files with 113 additions and 1 deletions
|
@ -16,6 +16,7 @@
|
||||||
"CardDetail.add-property": "+ Add a property",
|
"CardDetail.add-property": "+ Add a property",
|
||||||
"CardDetail.image": "Image",
|
"CardDetail.image": "Image",
|
||||||
"CardDetail.new-comment-placeholder": "Add a comment...",
|
"CardDetail.new-comment-placeholder": "Add a comment...",
|
||||||
|
"CardDetail.pick-icon": "Pick Icon",
|
||||||
"CardDetail.random-icon": "Random",
|
"CardDetail.random-icon": "Random",
|
||||||
"CardDetail.remove-icon": "Remove Icon",
|
"CardDetail.remove-icon": "Remove Icon",
|
||||||
"CardDetail.text": "Text",
|
"CardDetail.text": "Text",
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
"ViewHeader.test-add-100-cards": "TEST: Add 100 cards",
|
"ViewHeader.test-add-100-cards": "TEST: Add 100 cards",
|
||||||
"ViewHeader.test-add-1000-cards": "TEST: Add 1,000 cards",
|
"ViewHeader.test-add-1000-cards": "TEST: Add 1,000 cards",
|
||||||
"ViewHeader.test-randomize-icons": "TEST: Randomize icons",
|
"ViewHeader.test-randomize-icons": "TEST: Randomize icons",
|
||||||
|
"ViewTitle.pick-icon": "Pick Icon",
|
||||||
"ViewTitle.random-icon": "Random",
|
"ViewTitle.random-icon": "Random",
|
||||||
"ViewTitle.remove-icon": "Remove Icon",
|
"ViewTitle.remove-icon": "Remove Icon",
|
||||||
"ViewTitle.untitled-board": "Untitled Board"
|
"ViewTitle.untitled-board": "Untitled Board"
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"CardDetail.add-property": "+ Añadir propiedad",
|
"CardDetail.add-property": "+ Añadir propiedad",
|
||||||
"CardDetail.image": "Imagen",
|
"CardDetail.image": "Imagen",
|
||||||
"CardDetail.new-comment-placeholder": "Añadir un comentario...",
|
"CardDetail.new-comment-placeholder": "Añadir un comentario...",
|
||||||
|
"CardDetail.pick-icon": "Escoger Icono",
|
||||||
"CardDetail.random-icon": "Aleatorio",
|
"CardDetail.random-icon": "Aleatorio",
|
||||||
"CardDetail.remove-icon": "Quitar icono",
|
"CardDetail.remove-icon": "Quitar icono",
|
||||||
"CardDetail.text": "Texto",
|
"CardDetail.text": "Texto",
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
"ViewHeader.test-add-100-cards": "TEST: Añadir 100 tarjetas",
|
"ViewHeader.test-add-100-cards": "TEST: Añadir 100 tarjetas",
|
||||||
"ViewHeader.test-add-1000-cards": "TEST: Añadir 1,000 tarjetas",
|
"ViewHeader.test-add-1000-cards": "TEST: Añadir 1,000 tarjetas",
|
||||||
"ViewHeader.test-randomize-icons": "TEST: Iconos aleatorios",
|
"ViewHeader.test-randomize-icons": "TEST: Iconos aleatorios",
|
||||||
|
"ViewTitle.pick-icon": "Escoger Icono",
|
||||||
"ViewTitle.random-icon": "Aleatorio",
|
"ViewTitle.random-icon": "Aleatorio",
|
||||||
"ViewTitle.remove-icon": "Quitar Icono",
|
"ViewTitle.remove-icon": "Quitar Icono",
|
||||||
"ViewTitle.untitled-board": "Panel sin título"
|
"ViewTitle.untitled-board": "Panel sin título"
|
||||||
|
|
18
webapp/package-lock.json
generated
18
webapp/package-lock.json
generated
|
@ -1314,6 +1314,15 @@
|
||||||
"@types/tern": "*"
|
"@types/tern": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/emoji-mart": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/emoji-mart/-/emoji-mart-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-IAfTNRA6OoM7f/GZpuvdM47ktLGcHsnUuR+dQlwEEXSRbS0Z9iFoPA3WgBim6RBL4i7EVLtoRpREn5BgL3aMXA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/estree": {
|
"@types/estree": {
|
||||||
"version": "0.0.45",
|
"version": "0.0.45",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz",
|
||||||
|
@ -3627,6 +3636,15 @@
|
||||||
"integrity": "sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ==",
|
"integrity": "sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"emoji-mart": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-r5DXyzOLJttdwRYfJmPq/XL3W5tiAE/VsRnS0Hqyn27SqPA/GOYwVUSx50px/dXdJyDSnvmoPbuJ/zzhwSaU4A==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.0.0",
|
||||||
|
"prop-types": "^15.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"i18n-extract": "formatjs extract src/**/*.tsx src/**/*.ts --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json"
|
"i18n-extract": "formatjs extract src/**/*.tsx src/**/*.ts --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"emoji-mart": "^3.0.0",
|
||||||
"marked": "^1.1.1",
|
"marked": "^1.1.1",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"@formatjs/ts-transformer": "^2.11.3",
|
"@formatjs/ts-transformer": "^2.11.3",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.0.4",
|
"@testing-library/react": "^11.0.4",
|
||||||
|
"@types/emoji-mart": "^3.0.3",
|
||||||
"@types/jest": "^26.0.14",
|
"@types/jest": "^26.0.14",
|
||||||
"@types/marked": "^1.1.0",
|
"@types/marked": "^1.1.0",
|
||||||
"@types/react": "^16.9.49",
|
"@types/react": "^16.9.49",
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {Utils} from '../utils'
|
||||||
import MenuWrapper from '../widgets/menuWrapper'
|
import MenuWrapper from '../widgets/menuWrapper'
|
||||||
import Menu from '../widgets/menu'
|
import Menu from '../widgets/menu'
|
||||||
import Editable from '../widgets/editable'
|
import Editable from '../widgets/editable'
|
||||||
|
import EmojiPicker from '../widgets/emojiPicker'
|
||||||
import Button from '../widgets/buttons/button'
|
import Button from '../widgets/buttons/button'
|
||||||
|
|
||||||
import {MarkdownEditor} from './markdownEditor'
|
import {MarkdownEditor} from './markdownEditor'
|
||||||
|
@ -50,6 +51,13 @@ class CardDetail extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectEmoji = (emoji: string) => {
|
||||||
|
mutator.changeIcon(this.state.cardTree.card, emoji)
|
||||||
|
|
||||||
|
// Close the menu
|
||||||
|
document.body.click()
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.cardListener = new OctoListener()
|
this.cardListener = new OctoListener()
|
||||||
this.cardListener.open([this.props.cardId], async (blockId) => {
|
this.cardListener.open([this.props.cardId], async (blockId) => {
|
||||||
|
@ -128,6 +136,12 @@ class CardDetail extends React.Component<Props, State> {
|
||||||
name={intl.formatMessage({id: 'CardDetail.random-icon', defaultMessage: 'Random'})}
|
name={intl.formatMessage({id: 'CardDetail.random-icon', defaultMessage: 'Random'})}
|
||||||
onClick={() => mutator.changeIcon(card, BlockIcons.shared.randomIcon())}
|
onClick={() => mutator.changeIcon(card, BlockIcons.shared.randomIcon())}
|
||||||
/>
|
/>
|
||||||
|
<Menu.SubMenu
|
||||||
|
id='pick'
|
||||||
|
name={intl.formatMessage({id: 'CardDetail.pick-icon', defaultMessage: 'Pick Icon'})}
|
||||||
|
>
|
||||||
|
<EmojiPicker onSelect={this.onSelectEmoji}/>
|
||||||
|
</Menu.SubMenu>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='remove'
|
id='remove'
|
||||||
name={intl.formatMessage({id: 'CardDetail.remove-icon', defaultMessage: 'Remove Icon'})}
|
name={intl.formatMessage({id: 'CardDetail.remove-icon', defaultMessage: 'Remove Icon'})}
|
||||||
|
|
|
@ -203,6 +203,7 @@ class Sidebar extends React.Component<Props, State> {
|
||||||
<Menu.SubMenu
|
<Menu.SubMenu
|
||||||
id='lang'
|
id='lang'
|
||||||
name={intl.formatMessage({id: 'Sidebar.set-language', defaultMessage: 'Set Language'})}
|
name={intl.formatMessage({id: 'Sidebar.set-language', defaultMessage: 'Set Language'})}
|
||||||
|
position='top'
|
||||||
>
|
>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='english-lang'
|
id='english-lang'
|
||||||
|
@ -218,6 +219,7 @@ class Sidebar extends React.Component<Props, State> {
|
||||||
<Menu.SubMenu
|
<Menu.SubMenu
|
||||||
id='theme'
|
id='theme'
|
||||||
name={intl.formatMessage({id: 'Sidebar.set-theme', defaultMessage: 'Set Theme'})}
|
name={intl.formatMessage({id: 'Sidebar.set-theme', defaultMessage: 'Set Theme'})}
|
||||||
|
position='top'
|
||||||
>
|
>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='dark-theme'
|
id='dark-theme'
|
||||||
|
|
|
@ -9,6 +9,7 @@ import mutator from '../mutator'
|
||||||
import Menu from '../widgets/menu'
|
import Menu from '../widgets/menu'
|
||||||
import MenuWrapper from '../widgets/menuWrapper'
|
import MenuWrapper from '../widgets/menuWrapper'
|
||||||
import Editable from '../widgets/editable'
|
import Editable from '../widgets/editable'
|
||||||
|
import EmojiPicker from '../widgets/emojiPicker'
|
||||||
import Button from '../widgets/buttons/button'
|
import Button from '../widgets/buttons/button'
|
||||||
|
|
||||||
import './viewTitle.scss'
|
import './viewTitle.scss'
|
||||||
|
@ -33,6 +34,13 @@ class ViewTitle extends React.Component<Props, State> {
|
||||||
this.state = {title: props.board.title}
|
this.state = {title: props.board.title}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelectEmoji = (emoji: string) => {
|
||||||
|
mutator.changeIcon(this.props.board, emoji)
|
||||||
|
|
||||||
|
// Close the menu
|
||||||
|
document.body.click()
|
||||||
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
const {board, intl} = this.props
|
const {board, intl} = this.props
|
||||||
|
|
||||||
|
@ -62,6 +70,12 @@ class ViewTitle extends React.Component<Props, State> {
|
||||||
name={intl.formatMessage({id: 'ViewTitle.random-icon', defaultMessage: 'Random'})}
|
name={intl.formatMessage({id: 'ViewTitle.random-icon', defaultMessage: 'Random'})}
|
||||||
onClick={() => mutator.changeIcon(board, BlockIcons.shared.randomIcon())}
|
onClick={() => mutator.changeIcon(board, BlockIcons.shared.randomIcon())}
|
||||||
/>
|
/>
|
||||||
|
<Menu.SubMenu
|
||||||
|
id='pick'
|
||||||
|
name={intl.formatMessage({id: 'ViewTitle.pick-icon', defaultMessage: 'Pick Icon'})}
|
||||||
|
>
|
||||||
|
<EmojiPicker onSelect={this.onSelectEmoji}/>
|
||||||
|
</Menu.SubMenu>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
id='remove'
|
id='remove'
|
||||||
name={intl.formatMessage({id: 'ViewTitle.remove-icon', defaultMessage: 'Remove Icon'})}
|
name={intl.formatMessage({id: 'ViewTitle.remove-icon', defaultMessage: 'Remove Icon'})}
|
||||||
|
|
20
webapp/src/widgets/emojiPicker.scss
Normal file
20
webapp/src/widgets/emojiPicker.scss
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
.EmojiPicker {
|
||||||
|
.emoji-mart {
|
||||||
|
color: rgb(var(--main-fg));
|
||||||
|
background: rgb(var(--main-bg));
|
||||||
|
border-color: rgba(var(--main-fg), 0.16);
|
||||||
|
.emoji-mart-bar {
|
||||||
|
border: 0 solid rgba(var(--main-fg), 0.16);
|
||||||
|
border-bottom-width: 1px
|
||||||
|
}
|
||||||
|
.emoji-mart-search input {
|
||||||
|
border: 1px solid rgba(var(--main-fg), 0.16);
|
||||||
|
}
|
||||||
|
.emoji-mart-category-label span {
|
||||||
|
background: rgb(var(--main-bg));
|
||||||
|
}
|
||||||
|
.emoji-mart-search-icon svg {
|
||||||
|
fill: rgb(var(--main-fg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
webapp/src/widgets/emojiPicker.tsx
Normal file
25
webapp/src/widgets/emojiPicker.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
import React, {FC} from 'react'
|
||||||
|
|
||||||
|
import 'emoji-mart/css/emoji-mart.css'
|
||||||
|
import {Picker, BaseEmoji} from 'emoji-mart'
|
||||||
|
|
||||||
|
import './emojiPicker.scss'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSelect: (emoji: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmojiPicker: FC<Props> = (props: Props): JSX.Element => (
|
||||||
|
<div
|
||||||
|
className='EmojiPicker'
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Picker
|
||||||
|
onSelect={(emoji: BaseEmoji) => props.onSelect(emoji.native)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default EmojiPicker
|
|
@ -11,6 +11,12 @@
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: rgba(var(--main-fg), 0.05) 0px 0px 0px 1px, rgba(var(--main-fg), 0.1) 0px 3px 6px, rgba(var(--main-fg), 0.2) 0px 9px 24px;
|
box-shadow: rgba(var(--main-fg), 0.05) 0px 0px 0px 1px, rgba(var(--main-fg), 0.1) 0px 3px 6px, rgba(var(--main-fg), 0.2) 0px 9px 24px;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
|
&.top {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
&.bottom {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.SubmenuTriangleIcon {
|
.SubmenuTriangleIcon {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {MenuOptionProps} from './menuItem'
|
||||||
import './subMenuOption.scss'
|
import './subMenuOption.scss'
|
||||||
|
|
||||||
type SubMenuOptionProps = MenuOptionProps & {
|
type SubMenuOptionProps = MenuOptionProps & {
|
||||||
position?: 'bottom | top'
|
position?: 'bottom' | 'top'
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubMenuState = {
|
type SubMenuState = {
|
||||||
|
|
|
@ -47,6 +47,13 @@ function makeCommonConfig() {
|
||||||
'sass-loader',
|
'sass-loader',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/i,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.(tsx?|js|jsx|html)$/,
|
test: /\.(tsx?|js|jsx|html)$/,
|
||||||
use: [
|
use: [
|
||||||
|
|
Loading…
Reference in a new issue