diff --git a/src/assets/icons/emoji.svg b/src/assets/icons/emoji.svg new file mode 100644 index 0000000..98dd788 --- /dev/null +++ b/src/assets/icons/emoji.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/Buttons/ButtonGhost.tsx b/src/components/Buttons/ButtonGhost.tsx new file mode 100644 index 0000000..f6aed61 --- /dev/null +++ b/src/components/Buttons/ButtonGhost.tsx @@ -0,0 +1,26 @@ +import { Button } from '@kobalte/core'; +import { Component, JSXElement } from 'solid-js'; +import { hookForDev } from '../../lib/devTools'; + +import styles from './Buttons.module.scss'; + + +const ButtonGhost: Component<{ + id?: string, + onClick?: (e: MouseEvent) => void, + children?: JSXElement, + disabled?: boolean, +}> = (props) => { + return ( + + {props.children} + + ) +} + +export default hookForDev(ButtonGhost); diff --git a/src/components/Buttons/Buttons.module.scss b/src/components/Buttons/Buttons.module.scss index 37339c2..5cd094d 100644 --- a/src/components/Buttons/Buttons.module.scss +++ b/src/components/Buttons/Buttons.module.scss @@ -200,3 +200,21 @@ border: 1px solid var(--text-tertiary); } } + + +.ghost { + border-radius: 6px; + padding-inline: 10px; + padding-block: 6px; + font-size: 12px; + line-height: 16px; + font-weight: 700; + margin: 0px; + color: var(--text-primary); + background: none; + border: none; + + &:hover { + background: none; + } +} diff --git a/src/components/EmojiPickModal/EmojiPickModal.module.scss b/src/components/EmojiPickModal/EmojiPickModal.module.scss new file mode 100644 index 0000000..0d32c59 --- /dev/null +++ b/src/components/EmojiPickModal/EmojiPickModal.module.scss @@ -0,0 +1,88 @@ +.zapEmojiChangeModal { + position: fixed; + height: 344px; + color: var(--text-secondary); + background-color: var(--background-input); + border: 1px solid transparent; + border-radius: 6px; + + display: flex; + flex-direction: column; + padding-left: 22px; + padding-right: 16px; + padding-block: 22px; + + justify-content: flex-start; + align-items: center; + + input { + display: flex; + width: 70%; + height: 36px; + text-align: left; + margin: 0; + margin-bottom: 24px; + padding-inline: 16px; + font-size: 16px; + font-weight: 600; + line-height: 20px; + color: var(--text-primary); + background: none; + border: none; + border-radius: 18px; + + &:focus { + background-color: var(--subtile-devider); + outline: none; + box-shadow: none; + } + + &::placeholder { + color: var(--text-tertiary); + font-size: 16px; + font-weight: 400; + line-height: 20px; + } + } + + .title { + font-weight: 800; + font-size: 18px; + line-height: 18px; + color: var(--text-secondary); + text-transform: uppercase; + margin-bottom: 20px; + } + + .xClose { + background: none; + border: none; + margin: 0; + padding: 0; + width: fit-content; + + position: absolute; + top: 16px; + right: 22px; + + .iconClose { + width: 14px; + height: 14px; + display: inline-block; + margin: 0px 0px; + background-color: var(--text-secondary); + -webkit-mask: url(../../assets/icons/close.svg) no-repeat center; + mask: url(../../assets/icons/close.svg) no-repeat center; + } + + &:hover { + .iconClose { + background-color: var(--text-primary); + } + } + + &:focus { + box-shadow: none; + } + } +} diff --git a/src/components/EmojiPickModal/EmojiPickModal.tsx b/src/components/EmojiPickModal/EmojiPickModal.tsx new file mode 100644 index 0000000..2dd01b1 --- /dev/null +++ b/src/components/EmojiPickModal/EmojiPickModal.tsx @@ -0,0 +1,85 @@ +import { Component, createEffect, createSignal, For } from 'solid-js'; + +import styles from './EmojiPickModal.module.scss'; +import { useSettingsContext } from '../../contexts/SettingsContext'; +import { debounce, isVisibleInContainer, uuidv4 } from '../../utils'; +import { useIntl } from '@cookbook/solid-intl'; +import ConfirmModal from '../ConfirmModal/ConfirmModal'; +import { settings as t } from '../../translations'; +import { hookForDev } from '../../lib/devTools'; +import ButtonLink from '../Buttons/ButtonLink'; +import Modal from '../Modal/Modal'; + +import emojiSearch from '@jukben/emoji-search'; +import { createStore } from 'solid-js/store'; +import { EmojiOption } from '../../types/primal'; +import ButtonPrimary from '../Buttons/ButtonPrimary'; +import EmojiPicker from '../EmojiPicker/EmojiPicker'; + +const EmojiPickModal: Component<{ + id?: string, + open: boolean, + onClose: (e: MouseEvent | KeyboardEvent) => void, + onSelect: (emoji: EmojiOption) => void, +}> = (props) => { + + const intl = useIntl(); + + const [emojiSearchTerm, setEmojiSearchTerm] = createSignal('smile'); + + const onKey = (e: KeyboardEvent) => { + if (e.code === 'Escape') { + props.onClose(e); + return; + } + }; + + let emojiInput: HTMLInputElement | undefined; + + createEffect(() => { + if (props.open) { + window.addEventListener('keydown', onKey); + setTimeout(() => { + setEmojiSearchTerm(() => 'smile') + emojiInput?.focus(); + }, 10); + } + else { + window.removeEventListener('keydown', onKey); + } + }) + + return ( + props.onClose(e)} + > +
+
+ {intl.formatMessage(t.zapEmojiFilterTitle)} +
+ + + + { + const target = e.target as HTMLInputElement; + setEmojiSearchTerm(() => target.value); + }} + placeholder={intl.formatMessage(t.zapEmojiFilterPlaceholder)} + > + + + +
+
+ ); +} + +export default EmojiPickModal; diff --git a/src/components/NewNote/EditBox/EditBox.module.scss b/src/components/NewNote/EditBox/EditBox.module.scss index cb8863b..0da6a6e 100644 --- a/src/components/NewNote/EditBox/EditBox.module.scss +++ b/src/components/NewNote/EditBox/EditBox.module.scss @@ -79,6 +79,7 @@ margin-left: 8px; } .editorOptions { + display: flex; width: 100%; .attachIcon { @@ -93,6 +94,26 @@ background-color: var(--text-secondary); } } + .emojiIcon { + width: 21px; + height: 21px; + display: inline-block; + margin-right: 9px; + background-color: var(--text-tertiary); + cursor: pointer; + + &:hover { + background-color: var(--text-secondary); + } + } + + >button { + width: 26px; + height: 21px; + padding: 0; + margin-block: 0; + margin-inline: 8px; + } } diff --git a/src/components/NewNote/EditBox/EditBox.tsx b/src/components/NewNote/EditBox/EditBox.tsx index 65321c7..73622a8 100644 --- a/src/components/NewNote/EditBox/EditBox.tsx +++ b/src/components/NewNote/EditBox/EditBox.tsx @@ -39,6 +39,9 @@ import { hookForDev } from "../../../lib/devTools"; import ButtonPrimary from "../../Buttons/ButtonPrimary"; import ButtonSecondary from "../../Buttons/ButtonSecondary"; import { useProfileContext } from "../../../contexts/ProfileContext"; +import ButtonTertiary from "../../Buttons/ButtonTertiary"; +import ButtonGhost from "../../Buttons/ButtonGhost"; +import EmojiPickModal from "../../EmojiPickModal/EmojiPickModal"; type AutoSizedTextArea = HTMLTextAreaElement & { _baseScrollHeight: number }; @@ -483,6 +486,9 @@ const EditBox: Component<{ const onEscape = (e: KeyboardEvent) => { if (e.code === 'Escape') { + console.log('ESCAPE: ', isPickingEmoji()) + if (isPickingEmoji()) return; + !isMentioning() && !isEmojiInput() ? closeEditor() : closeEmojiAndMentions(); @@ -992,14 +998,14 @@ const EditBox: Component<{ // Get index of the token and insert emoji character const index = msg.slice(0, cursor).lastIndexOf(':'); - const value = msg.slice(0, index) + emoji.name + msg.slice(cursor); + const value = msg.slice(0, index) + `${emoji.name} ` + msg.slice(cursor); // Reset query, update message and text area value setMessage(value); textArea.value = message(); // Calculate new cursor position - textArea.selectionEnd = index + 1; + textArea.selectionEnd = index + 3; textArea.focus(); setEmojiInput(false); @@ -1148,6 +1154,30 @@ const EditBox: Component<{ callback && callback(); } + const [isPickingEmoji, setIsPickingEmoji] = createSignal(false); + + const addSelectedEmoji = (emoji: EmojiOption) => { + if (!textArea || !emoji) { + return; + } + + const msg = message(); + + // Get cursor position to determine insertion point + let cursor = textArea.selectionStart; + + // Get index of the token and insert emoji character + const value = msg.slice(0, cursor) + `${emoji.name} ` + msg.slice(cursor); + + // Reset query, update message and text area value + setMessage(value); + textArea.value = message(); + + // Calculate new cursor position + textArea.selectionEnd = cursor + 3; + textArea.focus(); + }; + return (
+ {setIsPickingEmoji(true)}}> +
+
+ + { + setTimeout(() => { + setIsPickingEmoji(false); + textArea?.focus(); + }, 100) + }} + onSelect={addSelectedEmoji} + /> ) } diff --git a/src/components/SettingsZap/SettingsZap.module.scss b/src/components/SettingsZap/SettingsZap.module.scss index a2461af..0b7f467 100644 --- a/src/components/SettingsZap/SettingsZap.module.scss +++ b/src/components/SettingsZap/SettingsZap.module.scss @@ -111,92 +111,3 @@ justify-content: flex-start; margin-top: 36px; } - -.zapEmojiChangeModal { - position: fixed; - height: 344px; - color: var(--text-secondary); - background-color: var(--background-input); - border: 1px solid transparent; - border-radius: 6px; - - display: flex; - flex-direction: column; - padding-left: 22px; - padding-right: 16px; - padding-block: 22px; - - justify-content: flex-start; - align-items: center; - - input { - display: flex; - width: 70%; - height: 36px; - text-align: left; - margin: 0; - margin-bottom: 24px; - padding-inline: 16px; - font-size: 16px; - font-weight: 600; - line-height: 20px; - color: var(--text-primary); - background: none; - border: none; - border-radius: 18px; - - &:focus { - background-color: var(--subtile-devider); - outline: none; - box-shadow: none; - } - - &::placeholder { - color: var(--text-tertiary); - font-size: 16px; - font-weight: 400; - line-height: 20px; - } - } - - .title { - font-weight: 800; - font-size: 18px; - line-height: 18px; - color: var(--text-secondary); - text-transform: uppercase; - margin-bottom: 20px; - } - - .xClose { - background: none; - border: none; - margin: 0; - padding: 0; - width: fit-content; - - position: absolute; - top: 16px; - right: 22px; - - .iconClose { - width: 14px; - height: 14px; - display: inline-block; - margin: 0px 0px; - background-color: var(--text-secondary); - -webkit-mask: url(../../assets/icons/close.svg) no-repeat center; - mask: url(../../assets/icons/close.svg) no-repeat center; - } - - &:hover { - .iconClose { - background-color: var(--text-primary); - } - } - - &:focus { - box-shadow: none; - } - } -} diff --git a/src/components/SettingsZap/SettingsZap.tsx b/src/components/SettingsZap/SettingsZap.tsx index e8b8c87..9fe6c20 100644 --- a/src/components/SettingsZap/SettingsZap.tsx +++ b/src/components/SettingsZap/SettingsZap.tsx @@ -15,6 +15,7 @@ import { createStore } from 'solid-js/store'; import { EmojiOption } from '../../types/primal'; import ButtonPrimary from '../Buttons/ButtonPrimary'; import EmojiPicker from '../EmojiPicker/EmojiPicker'; +import EmojiPickModal from '../EmojiPickModal/EmojiPickModal'; const SettingsZap: Component<{ id?: string }> = (props) => { @@ -176,35 +177,11 @@ const SettingsZap: Component<{ id?: string }> = (props) => { onAbort={() => setIsRestoringZaps(false)} /> - = 0} - onBackdropClick={() => setIsEmojiChange(-1)} - > -
-
- {intl.formatMessage(t.zapEmojiFilterTitle)} -
- - - - { - const target = e.target as HTMLInputElement; - setEmojiSearchTerm(() => target.value); - }} - placeholder={intl.formatMessage(t.zapEmojiFilterPlaceholder)} - > - - - -
-
+ onClose={() => setIsEmojiChange(-1)} + onSelect={changeZapOptionEmoji} + /> ); } diff --git a/src/index.scss b/src/index.scss index 9f72943..6de4b28 100644 --- a/src/index.scss +++ b/src/index.scss @@ -159,6 +159,10 @@ a { -webkit-mask: url(./assets/icons/attach_media.svg) no-repeat 0 / 100%; mask: url(./assets/icons/attach_media.svg) no-repeat 0 / 100%; } +.emoji_icon { + -webkit-mask: url(./assets/icons/emoji.svg) no-repeat 0 / 100%; + mask: url(./assets/icons/emoji.svg) no-repeat 0 / 100%; +} .bordered { border:solid 1px var(--subtile-devider);