diff --git a/CHANGELOG.md b/CHANGELOG.md index e4296f7cc7..306be29e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased][unreleased] +### Changed +- ContextMenuItem now can render icons +- use our own ContextMenu for AttachmentMenu +- replace BlueprintJS Button, Icon, Radio, RadioGroup, Collapse with our implementation + ## [v1.45.0] - 2024-05-24 diff --git a/images/icons/chats.svg b/images/icons/chats.svg new file mode 100644 index 0000000000..d8e68a62c1 --- /dev/null +++ b/images/icons/chats.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/chevron-left.svg b/images/icons/chevron-left.svg new file mode 100644 index 0000000000..2d083eb3ee --- /dev/null +++ b/images/icons/chevron-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/chevron-right.svg b/images/icons/chevron-right.svg new file mode 100644 index 0000000000..e59de717e9 --- /dev/null +++ b/images/icons/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/map.svg b/images/icons/map.svg new file mode 100644 index 0000000000..5330ab1fd5 --- /dev/null +++ b/images/icons/map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/paperclip.svg b/images/icons/paperclip.svg new file mode 100644 index 0000000000..e415fd48b6 --- /dev/null +++ b/images/icons/paperclip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/phone.svg b/images/icons/phone.svg new file mode 100644 index 0000000000..64b4220d94 --- /dev/null +++ b/images/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/icons/upload-file.svg b/images/icons/upload-file.svg new file mode 100644 index 0000000000..f60a62cce2 --- /dev/null +++ b/images/icons/upload-file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scss/_global.scss b/scss/_global.scss index 3428c6e265..2f628161e3 100644 --- a/scss/_global.scss +++ b/scss/_global.scss @@ -40,7 +40,6 @@ h3, h4, h5, p, -.bp4-button, button > span, label { user-select: none; diff --git a/scss/components/_radios.scss b/scss/components/_radios.scss index e15b6e5cf4..1731051bd2 100644 --- a/scss/components/_radios.scss +++ b/scss/components/_radios.scss @@ -15,7 +15,7 @@ width: 36px; } & > label { - padding-left: 4px; + padding-left: 7px; padding-right: 4px; height: 36px; display: inline-flex; diff --git a/scss/composer/_composer.scss b/scss/composer/_composer.scss index 4e8ab70b37..1bd207ba85 100644 --- a/scss/composer/_composer.scss +++ b/scss/composer/_composer.scss @@ -60,19 +60,20 @@ display: flex; align-items: flex-end; - .attachment-button, - .emoji-button { - .bp4-button.bp4-minimal { - width: 40px; - height: 40px; + .attachment-button { + width: 40px; + height: 40px; - &:hover { - background: none; - cursor: pointer; - } - &:focus { - outline: none; - } + background: none; + box-shadow: none; + border: none; + + &:hover { + background: none; + cursor: pointer; + } + &:focus { + outline: none; } } diff --git a/scss/main_screen/_navbar_wrapper.scss b/scss/main_screen/_navbar_wrapper.scss index 4dda818c0a..e5369e9153 100644 --- a/scss/main_screen/_navbar_wrapper.scss +++ b/scss/main_screen/_navbar_wrapper.scss @@ -44,12 +44,15 @@ margin-left: auto; margin-right: 15px; display: flex; + } - .bp4-button { - margin: 0px; - position: relative; - height: 50px; - border-radius: 0px; + .navbar-button { + padding: 0; + width: 40px; + height: 50px; + border-radius: 0; + &:hover { + background-color: var(--navBarButtonHover); } } @@ -66,7 +69,6 @@ } #three-dot-menu-button { - width: 40px; height: 40px; border-radius: 100%; } diff --git a/scss/misc/_context_menu.scss b/scss/misc/_context_menu.scss index ad8c1eff91..93ddc87324 100644 --- a/scss/misc/_context_menu.scss +++ b/scss/misc/_context_menu.scss @@ -19,8 +19,8 @@ // position is determined in code position: fixed; pointer-events: auto; - background-color: var(--bp4MenuBg); - color: var(--bp4MenuText); + background-color: var(--bgPrimary); + color: var(--textPrimary); border-radius: 3px; overflow: hidden; @@ -36,14 +36,20 @@ white-space: nowrap; display: flex; align-items: center; - justify-content: space-between; + justify-content: flex-start; + + & .left-icon { + background-color: var(--iconBackground); + margin: 0 5px 0 0; + } & .right-icon { width: 18px; height: 18px; + margin-left: auto; @include color-svg( '../images/icons/navigate_next.svg', - var(--bp4MenuText), + var(--textPrimary), 100% ); transform: translate(11px, -0.07em); diff --git a/src/renderer/components/Button/index.tsx b/src/renderer/components/Button/index.tsx index 70240e86d0..ca358758ab 100644 --- a/src/renderer/components/Button/index.tsx +++ b/src/renderer/components/Button/index.tsx @@ -7,6 +7,7 @@ type Props = React.PropsWithChildren<{ 'aria-label'?: string className?: string disabled?: boolean + active?: boolean id?: string onClick: any type?: 'primary' | 'secondary' | 'danger' @@ -15,6 +16,7 @@ type Props = React.PropsWithChildren<{ export default function Button({ children, disabled = false, + active = false, id, onClick, type, @@ -27,6 +29,7 @@ export default function Button({ role='button' className={classNames( styles.button, + active && styles.active, type && styles[type], props.className )} diff --git a/src/renderer/components/Button/style.module.scss b/src/renderer/components/Button/style.module.scss index 33c72a376b..de14827b5b 100644 --- a/src/renderer/components/Button/style.module.scss +++ b/src/renderer/components/Button/style.module.scss @@ -12,6 +12,11 @@ padding: 12px 15px; text-align: center; text-transform: uppercase; + user-select: none; + + & > span { + user-select: none; + } &:hover { background-color: var(--buttonHover); @@ -31,6 +36,10 @@ color: var(--buttonDangerText); } + &.active { + background-color: var(--navBarButtonActive); + } + &:disabled { background-color: var(--buttonDisabledBackground); color: var(--buttonDisabledText); diff --git a/src/renderer/components/Collapse/index.tsx b/src/renderer/components/Collapse/index.tsx new file mode 100644 index 0000000000..000e391223 --- /dev/null +++ b/src/renderer/components/Collapse/index.tsx @@ -0,0 +1,49 @@ +import React, { useLayoutEffect, useRef } from 'react' + +type Props = { + className?: string + isOpen: boolean + children: any +} + +export default function Collapse({ + className, + children, + isOpen = false, +}: Props) { + const content = useRef(null) + const wrapper = useRef(null) + + useLayoutEffect(() => { + if (!content.current || !wrapper.current) { + return + } + const bounds = content.current.getBoundingClientRect() + + const maxHeight = isOpen ? `${bounds?.height}px` : '0px' + const translateY = isOpen ? 0 : `-${bounds?.height}px` + + wrapper.current.style.height = maxHeight + + content.current.style.transform = `translate(0px, ${translateY})` + }) + return ( +
+
+ {children} +
+
+ ) +} diff --git a/src/renderer/components/ContextMenu.tsx b/src/renderer/components/ContextMenu.tsx index e86dff14ce..32553a8466 100644 --- a/src/renderer/components/ContextMenu.tsx +++ b/src/renderer/components/ContextMenu.tsx @@ -6,15 +6,19 @@ import React, { useCallback, } from 'react' import classNames from 'classnames' +import Icon from './Icon' +import type { IconName } from './Icon' import useContextMenu from '../hooks/useContextMenu' type ContextMenuItemActionable = { + icon?: IconName action: (event: React.MouseEvent) => void subitems?: never } type ContextMenuItemExpandable = { + icon?: IconName action?: never subitems: (ContextMenuItem | undefined)[] } @@ -344,6 +348,7 @@ export function ContextMenu(props: { key={index} {...(item.subitems && { 'data-expandable-index': index })} > + {item.icon && } {item.label} {item.subitems &&
} diff --git a/src/renderer/components/Icon/index.tsx b/src/renderer/components/Icon/index.tsx index 577ad81b68..7b68980163 100644 --- a/src/renderer/components/Icon/index.tsx +++ b/src/renderer/components/Icon/index.tsx @@ -8,6 +8,9 @@ export type IconName = | 'audio-muted' | 'bell' | 'brightness-6' + | 'chats' + | 'chevron-left' + | 'chevron-right' | 'code-tags' | 'cross' | 'devices' @@ -17,27 +20,51 @@ export type IconName = | 'info' | 'lead-pencil' | 'list' + | 'map' + | 'media' + | 'minus' | 'more' | 'open_in_new' + | 'paperclip' | 'person' | 'person-filled' + | 'phone' | 'qr' | 'question_mark' | 'reaction' | 'settings' | 'swap_vert' + | 'tint' + | 'undo' + | 'upload-file' type Props = { className?: string + onClick?: (ev: Event | React.SyntheticEvent) => void icon: IconName + coloring?: string size?: number + rotation?: number } -export default function Icon({ size = 20, icon, className }: Props) { +export default function Icon({ + coloring = 'primary', + size = 20, + rotation = 0, + icon, + className, + onClick, +}: Props) { return ( {i !== 8 && i !== 2 && i !== 5 && (
- +
)} diff --git a/src/renderer/components/LoginForm.tsx b/src/renderer/components/LoginForm.tsx index 8b86faed49..3e7b03b29a 100644 --- a/src/renderer/components/LoginForm.tsx +++ b/src/renderer/components/LoginForm.tsx @@ -2,7 +2,6 @@ import { C, DcEventType } from '@deltachat/jsonrpc-client' import React, { useEffect, useRef, useState } from 'react' -import { Collapse } from '@blueprintjs/core' import { useDebouncedCallback } from 'use-debounce/lib' import { @@ -24,6 +23,7 @@ import Dialog, { FooterActionButton, FooterActions, } from './Dialog' +import Collapse from './Collapse' import { I18nContext } from '../contexts/I18nContext' import useTranslationFunction from '../hooks/useTranslationFunction' import { getDeviceChatId, saveLastChatId } from '../backend/chat' diff --git a/src/renderer/components/SearchInput/styles.module.scss b/src/renderer/components/SearchInput/styles.module.scss index be46dbb802..4dd554e248 100644 --- a/src/renderer/components/SearchInput/styles.module.scss +++ b/src/renderer/components/SearchInput/styles.module.scss @@ -1,4 +1,3 @@ -$backgroundColorHover: rgba(143, 153, 168, 0.15); $borderRadius: 10px; $closeButtonSize: 30px; @@ -38,7 +37,7 @@ $closeButtonSize: 30px; min-width: $closeButtonSize; &:hover { - background-color: $backgroundColorHover; + background-color: var(--navBarButtonHover); } } diff --git a/src/renderer/components/Settings/Appearance.tsx b/src/renderer/components/Settings/Appearance.tsx index 8103cb509d..b1223bb9ce 100644 --- a/src/renderer/components/Settings/Appearance.tsx +++ b/src/renderer/components/Settings/Appearance.tsx @@ -1,6 +1,5 @@ import { join } from 'path' import React, { useEffect, useState } from 'react' -import { Icon } from '@blueprintjs/core' import { ThemeManager } from '../../ThemeManager' import { runtime } from '../../runtime' @@ -21,6 +20,7 @@ import { DialogContent } from '../Dialog' import useTranslationFunction from '../../hooks/useTranslationFunction' import useDialog from '../../hooks/dialog/useDialog' import { LastUsedSlot, rememberLastUsedPath } from '../../utils/lastUsedPaths' +import Icon from '../Icon' const log = getLogger('renderer/settings/appearance') diff --git a/src/renderer/components/SmallSelectDialog.tsx b/src/renderer/components/SmallSelectDialog.tsx index cda653dc01..a3ec9a9e62 100644 --- a/src/renderer/components/SmallSelectDialog.tsx +++ b/src/renderer/components/SmallSelectDialog.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react' -import { RadioGroup, Radio } from '@blueprintjs/core' import Dialog, { DialogBody, @@ -8,6 +7,8 @@ import Dialog, { DialogHeader, FooterActions, } from './Dialog' +import Radio from './Radio' +import RadioGroup from './RadioGroup' import FooterActionButton from './Dialog/FooterActionButton' import useTranslationFunction from '../hooks/useTranslationFunction' @@ -38,10 +39,9 @@ export default function SmallSelectDialog({ const [actualSelectedValue, setActualSelectedValue] = useState(selectedValue) - const onChange = (event: React.FormEvent) => { - const actualSelectedValue = String(event.currentTarget.value) - setActualSelectedValue(actualSelectedValue) - onSelect && onSelect(actualSelectedValue) + const onChange = (value: string) => { + setActualSelectedValue(value) + onSelect && onSelect(value) } const saveAndClose = () => { @@ -49,12 +49,17 @@ export default function SmallSelectDialog({ onClose() } + // FIXME(maxph): get a name for RadioGroup return ( - + {values.map((element, index) => { const [value, label] = element return ( diff --git a/src/renderer/components/composer/menuAttachment.tsx b/src/renderer/components/composer/menuAttachment.tsx index b841616154..01275edd65 100644 --- a/src/renderer/components/composer/menuAttachment.tsx +++ b/src/renderer/components/composer/menuAttachment.tsx @@ -1,12 +1,4 @@ -import React, { useCallback } from 'react' -import { - Button, - Position, - Popover, - Menu, - MenuItem, - IconName, -} from '@blueprintjs/core' +import React, { useCallback, useContext } from 'react' import { dirname } from 'path' import { runtime } from '../../runtime' @@ -18,6 +10,10 @@ import useTranslationFunction from '../../hooks/useTranslationFunction' import useVideoChat from '../../hooks/useVideoChat' import { LastUsedSlot, rememberLastUsedPath } from '../../utils/lastUsedPaths' import { selectedAccountId } from '../../ScreenController' +import Icon from '../Icon' + +import { ContextMenuItem } from '../ContextMenu' +import { ContextMenuContext } from '../../contexts/ContextMenuContext' import type { T } from '@deltachat/jsonrpc-client' @@ -26,18 +22,13 @@ type Props = { selectedChat: T.FullChat | null } -type MenuAttachmentItemObject = { - id: number - icon: IconName - text: string - onClick: todo -} - // Main component that creates the menu and popover export default function MenuAttachment({ addFileToDraft, selectedChat, }: Props) { + const { openContextMenu } = useContext(ContextMenuContext) + const tx = useTranslationFunction() const openConfirmationDialog = useConfirmationDialog() const { sendVideoChatInvitation } = useVideoChat() @@ -111,59 +102,48 @@ export default function MenuAttachment({ ]) // item array used to populate menu - const items: MenuAttachmentItemObject[] = [ - settings?.settings.webrtc_instance && { - id: 0, - icon: 'phone' as IconName, - text: tx('videochat'), - onClick: onVideoChat, + const menu: (ContextMenuItem | false)[] = [ + settings?.settings.webrtc_instance !== undefined && { + icon: 'phone', + label: tx('videochat'), + action: onVideoChat, }, { - id: 1, - icon: 'media' as IconName, - text: tx('image'), - onClick: addFilenameMedia.bind(null), + icon: 'image', + label: tx('image'), + action: addFilenameMedia.bind(null), }, { - id: 2, - icon: 'document' as IconName, - text: tx('file'), - onClick: addFilenameFile.bind(null), + icon: 'upload-file', + label: tx('file'), + action: addFilenameFile.bind(null), }, - ].filter(item => !!item) as MenuAttachmentItemObject[] + ] - return ( -
- - - - } - position={Position.TOP_LEFT} - > -
- ) -} + const onClickAttachmentMenu = (event: React.MouseEvent) => { + const threeDotButtonElement = document.querySelector( + '#attachment-menu-button' + ) as HTMLDivElement + + const boundingBox = threeDotButtonElement.getBoundingClientRect() + + const [cursorX, cursorY] = [boundingBox.x, boundingBox.y] + event.preventDefault() // prevent default runtime context menu from opening + + openContextMenu({ + cursorX, + cursorY, + items: menu, + }) + } -// Function to populate Menu -const MenuAttachmentItems = ({ - itemsArray, -}: { - itemsArray: MenuAttachmentItemObject[] -}) => { return ( - <> - {itemsArray.map((item: MenuAttachmentItemObject) => ( - - ))} - + ) } diff --git a/src/renderer/components/contact/ContactListItem.tsx b/src/renderer/components/contact/ContactListItem.tsx index f837ff39c4..b42862c5cc 100644 --- a/src/renderer/components/contact/ContactListItem.tsx +++ b/src/renderer/components/contact/ContactListItem.tsx @@ -1,8 +1,8 @@ import React from 'react' import Contact from './Contact' -import { Icon } from '@blueprintjs/core' import classNames from 'classnames' import { Type } from '../../backend-com' +import Icon from '../Icon' export const DeltaCheckbox = (props: { checked: boolean diff --git a/src/renderer/components/dialogs/DisappearingMessages.tsx b/src/renderer/components/dialogs/DisappearingMessages.tsx index f7064e91ea..dd39d81ab2 100644 --- a/src/renderer/components/dialogs/DisappearingMessages.tsx +++ b/src/renderer/components/dialogs/DisappearingMessages.tsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from 'react' -import { RadioGroup, Radio } from '@blueprintjs/core' import { Timespans } from '../../../shared/constants' import { BackendRemote } from '../../backend-com' @@ -12,6 +11,8 @@ import Dialog, { FooterActionButton, FooterActions, } from '../Dialog' +import Radio from '../Radio' +import RadioGroup from '../RadioGroup' import useTranslationFunction from '../../hooks/useTranslationFunction' import type { DialogProps } from '../../contexts/DialogContext' @@ -38,13 +39,13 @@ function SelectDisappearingMessageDuration({ }) { const tx = useTranslationFunction() - const onChange = (ev: React.FormEvent) => { - const disappearingMessageDuration = Number(ev.currentTarget.value) - onSelectDisappearingMessageDuration(disappearingMessageDuration) + const onChange = (duration: string) => { + onSelectDisappearingMessageDuration(Number(duration)) } return ( diff --git a/src/renderer/components/dialogs/FullscreenAvatar.tsx b/src/renderer/components/dialogs/FullscreenAvatar.tsx index 57315f2c84..1ce3131f68 100644 --- a/src/renderer/components/dialogs/FullscreenAvatar.tsx +++ b/src/renderer/components/dialogs/FullscreenAvatar.tsx @@ -1,6 +1,6 @@ import React, { useRef } from 'react' import { basename } from 'path' -import { Icon, Overlay } from '@blueprintjs/core' +import { Overlay } from '@blueprintjs/core' import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch' import { runtime } from '../../runtime' @@ -8,6 +8,7 @@ import useTranslationFunction from '../../hooks/useTranslationFunction' import useContextMenu from '../../hooks/useContextMenu' import type { DialogProps } from '../../contexts/DialogContext' +import Icon from '../Icon' export default function FullscreenAvatar( props: { imagePath: string } & DialogProps @@ -71,7 +72,7 @@ export default function FullscreenAvatar( onClick={onClose} icon='cross' size={32} - color={'grey'} + coloring='settings-menu' aria-label={tx('close')} /> diff --git a/src/renderer/components/dialogs/FullscreenMedia.tsx b/src/renderer/components/dialogs/FullscreenMedia.tsx index ea0038a8d9..b235f0d1aa 100644 --- a/src/renderer/components/dialogs/FullscreenMedia.tsx +++ b/src/renderer/components/dialogs/FullscreenMedia.tsx @@ -1,8 +1,9 @@ import React, { useEffect, useState, useCallback, useRef, useMemo } from 'react' -import { Icon, Overlay } from '@blueprintjs/core' +import { Overlay } from '@blueprintjs/core' import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch' import debounce from 'debounce' +import Icon from '../Icon' import { onDownload } from '../message/messageFunctions' import { runtime } from '../../runtime' import { isImage, isVideo, isAudio } from '../attachment/Attachment' @@ -255,7 +256,7 @@ export default function FullscreenMedia(props: Props & DialogProps) { onClick={onClose} icon='cross' size={32} - color={'grey'} + coloring='settings-menu' aria-label={tx('close')} /> diff --git a/src/renderer/components/screens/MainScreen.tsx b/src/renderer/components/screens/MainScreen.tsx index 4ca1631a55..1a064d7c83 100644 --- a/src/renderer/components/screens/MainScreen.tsx +++ b/src/renderer/components/screens/MainScreen.tsx @@ -2,11 +2,9 @@ import React, { useState, useRef, useEffect, useCallback } from 'react' import { C } from '@deltachat/jsonrpc-client' import { Alignment, - Classes, Navbar, NavbarGroup, NavbarHeading, - Button, } from '@blueprintjs/core' import Gallery from '../Gallery' @@ -18,6 +16,8 @@ import MailingListProfile from '../dialogs/MessageListProfile' import SettingsStoreInstance, { useSettingsStore } from '../../stores/settings' import { Type } from '../../backend-com' import { InlineVerifiedIcon } from '../VerifiedIcon' +import Button from '../Button' +import Icon from '../Icon' import SearchInput from '../SearchInput' import MessageListView from '../MessageListView' import useChat from '../../hooks/chat/useChat' @@ -163,16 +163,14 @@ export default function MainScreen({ accountId }: Props) { {tx('chat_archived_chats_title')} )} {!showArchivedChats && ( @@ -202,13 +200,13 @@ export default function MainScreen({ accountId }: Props) { aria-disabled={threeDotMenuHidden} >
)} @@ -362,29 +360,29 @@ function ChatNavButtons() { {settingsStore?.desktopSettings.enableOnDemandLocationStreaming && ( )} diff --git a/test/testcafe/messagelist_tests.ts b/test/testcafe/messagelist_tests.ts index bbd8cbf0aa..846e20518b 100644 --- a/test/testcafe/messagelist_tests.ts +++ b/test/testcafe/messagelist_tests.ts @@ -173,7 +173,7 @@ export async function goToSideBarSubSettingsMenu(t, label) { } export async function sendVideoChatInvitation(t) { - await t.click('#test-attachment-menu') + await t.click('#attachment-menu-button') await t.click( Selector('a.bp4-menu-item').withText(await translate('videochat')) ) diff --git a/themes/_themebase.scss b/themes/_themebase.scss index 972a7b9524..cc1ec5e8c9 100644 --- a/themes/_themebase.scss +++ b/themes/_themebase.scss @@ -27,9 +27,13 @@ $hover-contrast-change: 2%; --buttonDangerText: #{$colorDanger}; --buttonDisabledBackground: #{$bgSecondary}; --buttonDisabledText: #{changeContrast($textSecondary, 45%)}; + /* Icons */ + --iconBackground: #757575; /* color from emoji.png */ /* NavBar */ --navBarBackground: #{$bgNavBar}; --navBarText: #{$textNavBar}; + --navBarButtonHover: rgba(143, 153, 168, 0.15); + --navBarButtonActive: rgba(143, 153, 168, 0.3); --navBarSearchPlaceholder: #{changeContrast($textNavBar, 27%)}; --navBarGroupSubtitle: #{changeContrast($textNavBar, 27%)}; /* ChatView */