integrate styles of messages' tooltip
Change-Id: I4f66cfc5fe561a9eaa90e6eff7624f839b45d51f
diff --git a/client/src/components/Message.js b/client/src/components/Message.js
index e22d863..c7f22f1 100644
--- a/client/src/components/Message.js
+++ b/client/src/components/Message.js
@@ -1,10 +1,13 @@
-import { Box, Chip, Divider, Stack, Typography } from "@mui/material"
+import { Box, Chip, Divider, List, ListItemButton, ListItemText, Stack, Tooltip, Typography } from "@mui/material"
+import { styled } from "@mui/material/styles";
import dayjs from "dayjs"
import isToday from "dayjs/plugin/isToday"
import isYesterday from "dayjs/plugin/isYesterday"
-import { useMemo } from "react";
+import React, { useCallback, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
+import { EmojiButton, MoreButton, ReplyMessageButton } from "./buttons";
import ConversationAvatar from "./ConversationAvatar"
+import { OppositeArrowsIcon, TrashBinIcon, TwoSheetsIcon } from "./svgIcons"
dayjs.extend(isToday)
dayjs.extend(isYesterday)
@@ -216,6 +219,131 @@
)
}
+const MessageTooltip = styled(({ className, ...props }) => {
+ const [open, setOpen] = useState(false)
+ const emojis = ["😎", "😄", "😍"] // Should be last three used emojis
+ const additionalOptions = [
+ {
+ Icon: TwoSheetsIcon,
+ text: "Copy",
+ action: () => {},
+ },
+ {
+ Icon: OppositeArrowsIcon,
+ text: "Transfer",
+ action: () => {},
+ },
+ {
+ Icon: TrashBinIcon,
+ text: "Delete message",
+ action: () => {},
+ },
+ ]
+
+ const toggleMoreMenu = useCallback(
+ () => setOpen(open => !open),
+ [setOpen]
+ )
+
+ const onClose = useCallback(
+ () => {
+ setOpen(false)
+ },
+ [setOpen]
+ )
+
+ return (
+ <Tooltip
+ {...props}
+ classes={{ tooltip: className }} // Required for styles. Don't know why
+ placement={props.position == "start" ? "right-start" : "left-start"}
+ PopperProps={{
+ modifiers: [
+ {
+ name: "offset",
+ options: {
+ offset: [-2, -30],
+ },
+ },
+ ],
+ }}
+ onClose={onClose}
+ title={
+ <Stack> {/* Whole tooltip's content */}
+ <Stack // Main options
+ direction="row"
+ spacing="16px"
+ >
+ {
+ emojis.map(
+ emoji => <EmojiButton key={emoji} emoji={emoji}/>
+ )
+ }
+ <ReplyMessageButton/>
+ <MoreButton
+ onClick={toggleMoreMenu}
+ />
+ </Stack>
+ {open && // Additional menu options
+ <>
+ <Divider sx={{paddingTop:"16px"}}/>
+ <List sx={{padding: 0, paddingTop: "8px", marginBottom: "-8px"}}>
+ {
+ additionalOptions.map(
+ (option) => (
+ <ListItemButton
+ key={option.text}
+ sx={{
+ padding: "8px",
+ }}
+ >
+ <Stack // Could not find proper way to set spacing between ListItemIcon and ListItemText
+ direction="row"
+ spacing="16px"
+ >
+ <option.Icon
+ sx={{
+ height: "16px",
+ margin: 0,
+ color: theme => theme.palette.primary.dark,
+ }}
+ />
+ <ListItemText
+ primary={option.text}
+ primaryTypographyProps={{
+ fontSize: "12px",
+ lineHeight: "16px",
+ }}
+ sx={{
+ height: "16px",
+ margin: 0,
+ }}
+ />
+ </Stack>
+ </ListItemButton>
+ )
+ )
+ }
+ </List>
+ </>
+ }
+ </Stack>
+ }
+ />
+ )
+})(({ theme, position }) => {
+ const largeRadius = "20px"
+ const smallRadius = "5px"
+ return {
+ backgroundColor: "white",
+ padding: "16px",
+ boxShadow: "3px 3px 7px #00000029",
+ borderRadius: largeRadius,
+ borderStartStartRadius: position == "start" ? smallRadius : largeRadius,
+ borderStartEndRadius: position == "end" ? smallRadius : largeRadius,
+ }
+});
+
const MessageBubble = (props) => {
const largeRadius = "20px"
const smallRadius = "5px"
@@ -237,16 +365,20 @@
}, [props.isFirstOfGroup, props.isLastOfGroup, props.position])
return (
- <Box
- sx={{
- width: "fit-content",
- backgroundColor: props.backgroundColor,
- padding: "16px",
- ...radius,
- }}
+ <MessageTooltip
+ position={props.position}
>
- {props.children}
- </Box>
+ <Box
+ sx={{
+ width: "fit-content",
+ backgroundColor: props.backgroundColor,
+ padding: "16px",
+ ...radius,
+ }}
+ >
+ {props.children}
+ </Box>
+ </MessageTooltip>
)
}
diff --git a/client/src/components/buttons.js b/client/src/components/buttons.js
index 7ea9802..d0db1c0 100644
--- a/client/src/components/buttons.js
+++ b/client/src/components/buttons.js
@@ -2,8 +2,8 @@
import { Box, ClickAwayListener, IconButton, Popper } from "@mui/material";
import { styled } from "@mui/material/styles";
import EmojiPicker from "emoji-picker-react";
-import { useState, useCallback } from "react";
-import { Arrow2Icon, ArrowIcon, CameraIcon, CameraInBubbleIcon, CancelIcon, CrossedEyeIcon, CrossIcon, EmojiIcon, EyeIcon, FolderIcon, InfoIcon, MicroInBubbleIcon, PaperClipIcon, PenIcon } from "./svgIcons";
+import React, { useState, useCallback } from "react";
+import { Arrow2Icon, Arrow3Icon, ArrowIcon, CameraIcon, CameraInBubbleIcon, CancelIcon, CrossedEyeIcon, CrossIcon, EmojiIcon, EyeIcon, FolderIcon, InfoIcon, MicroInBubbleIcon, PaperClipIcon, PenIcon, SaltireIcon } from "./svgIcons";
const RoundButton = styled(
({Icon, ...props}) => (
@@ -96,6 +96,33 @@
)
}
+export const MoreButton = styled(
+ (props) => {
+ return (
+ <IconButton
+ {...props}
+ disableRipple={true}
+ aria-label="more"
+ >
+ <CrossIcon fontSize="inherit"/>
+ </IconButton>
+ )
+ }
+)(({theme}) => ({
+ border: `1px solid ${theme.palette.primary.dark}`,
+ color: theme.palette.primary.dark,
+ fontSize: "10px",
+ height: "20px",
+ width: "20px",
+ "&:hover": {
+ background: theme.palette.primary.light,
+ },
+ "&:active": {
+ color: "#FFF",
+ background: theme.palette.primary.dark,
+ },
+}))
+
export const BackButton = styled(
(props) => {
return (
@@ -127,7 +154,7 @@
disableRipple={true}
aria-label="close"
>
- <CrossIcon fontSize="inherit"/>
+ <SaltireIcon fontSize="inherit"/>
</IconButton>
)
}
@@ -214,6 +241,40 @@
)
}
+export const ReplyMessageButton = styled(
+ ({Icon, ...props}) => (
+ <IconButton
+ {...props}
+ disableRipple={true}
+ aria-label="send message"
+ >
+ <Arrow3Icon fontSize="inherit"/>
+ </IconButton>
+ )
+)(({theme}) => ({
+ color: theme.palette.primary.dark,
+ fontSize: "20px",
+ height: "20px",
+ width: "20px",
+ borderRadius: "5px",
+ "&:hover": {
+ background: "#E5E5E5",
+ },
+}));
+
+export const EmojiButton = styled(
+ ({emoji, ...props}) => (
+ <IconButton {...props} disableRipple={true}>
+ {emoji}
+ </IconButton>
+ )
+)(({theme}) => ({
+ color: "white",
+ fontSize: "20px",
+ height: "20px",
+ width: "20px",
+}));
+
export const SelectEmojiButton = (props) => {
const [anchorEl, setAnchorEl] = useState(null)
diff --git a/client/src/components/inputs.js b/client/src/components/inputs.js
index a0315d3..aec48d3 100644
--- a/client/src/components/inputs.js
+++ b/client/src/components/inputs.js
@@ -2,11 +2,11 @@
import { styled } from "@mui/material/styles"
import { useState, useCallback, useEffect } from "react";
import { InfoButton, ToggleVisibilityButton } from "./buttons"
-import { CheckedIcon, RoundCrossIcon, LockIcon, PenIcon, PersonIcon } from "./svgIcons"
+import { CheckedIcon, RoundSaltireIcon, LockIcon, PenIcon, PersonIcon } from "./svgIcons"
const iconsHeight = "16px"
const StyledCheckedIconSuccess = styled(CheckedIcon)(({theme}) => ({height: iconsHeight, color: theme.palette.success.main}))
-const StyledRoundCrossIconError = styled(RoundCrossIcon)(({theme}) => ({height: iconsHeight, color: theme.palette.error.main}))
+const StyledRoundSaltireIconError = styled(RoundSaltireIcon)(({theme}) => ({height: iconsHeight, color: theme.palette.error.main}))
const StyledPenIconLight = styled(PenIcon)({height: iconsHeight, color: "#03B9E9"})
const StyledPenIconDark = styled(PenIcon)(({theme}) => ({height: iconsHeight, color: theme.palette.primary.dark}))
const StyledPersonIconLight = styled(PersonIcon)({height: iconsHeight, color: "#03B9E9"})
@@ -27,7 +27,7 @@
let Icon = StyledPersonIconLight
let visibility = "visible"
if (props.error) {
- Icon = StyledRoundCrossIconError
+ Icon = StyledRoundSaltireIconError
}
else if (props.success) {
Icon = StyledCheckedIconSuccess
@@ -75,7 +75,7 @@
let Icon = StyledLockIcon
let visibility = "visible"
if (props.error) {
- Icon = StyledRoundCrossIconError
+ Icon = StyledRoundSaltireIconError
}
else if (props.success) {
Icon = StyledCheckedIconSuccess
diff --git a/client/src/components/svgIcons.js b/client/src/components/svgIcons.js
index 73275ee..621d457 100644
--- a/client/src/components/svgIcons.js
+++ b/client/src/components/svgIcons.js
@@ -28,6 +28,14 @@
)
}
+export const Arrow3Icon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 21.045 14.743">
+ <path d="m15.54 32.48 8.635 6.49a.2.2 0 0 0 .319-.174V34.74c9.823.666 10.982 4.984 10.982 4.984 0-8.982-7.186-10.228-10.982-10.257v-3.651a.187.187 0 0 0-.319-.145l-8.635 6.49a.227.227 0 0 0 0 .319Z" transform="translate(-14.93 -25.11)" style={{fill:"none", stroke:"#005699"}}/>
+ </SvgIcon>
+ )
+}
+
export const CameraIcon = (props) => {
return (
<SvgIcon {...props} viewBox="2 3 20 19">
@@ -73,6 +81,14 @@
export const CrossIcon = (props) => {
return (
+ <SvgIcon {...props} viewBox="0 0 10 10">
+ <path d="M32 979.362a.652.652 0 0 0-.652.652v3.7h-3.7a.652.652 0 0 0 0 1.3h3.7v3.7a.652.652 0 0 0 1.3 0v-3.7h3.7a.652.652 0 0 0 0-1.3h-3.7v-3.7a.652.652 0 0 0-.648-.652Z" transform="translate(-27 -979.362)"/>
+ </SvgIcon>
+ )
+}
+
+export const SaltireIcon = (props) => {
+ return (
<SvgIcon {...props} viewBox="5 5 14 14">
<path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
</SvgIcon>
@@ -143,6 +159,14 @@
)
}
+export const OppositeArrowsIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 16.123 17" style={{fill:"none", stroke: "#005699", fillRule:"evenodd"}}>
+ <path d="M15.623 11.025a.358.358 0 0 0-.358-.358h-8.1v-1.4a.358.358 0 0 0-.537-.31l-2.972 1.717-2.977 1.72a.358.358 0 0 0 0 .62l2.977 1.718 2.975 1.719a.358.358 0 0 0 .537-.31v-1.4h8.1a.358.358 0 0 0 .358-.358v-3.36ZM.5 2.615a.358.358 0 0 1 .358-.358h8.1v-1.4a.358.358 0 0 1 .537-.31l2.976 1.718 2.977 1.72a.358.358 0 0 1 0 .62l-2.977 1.718-2.978 1.719a.358.358 0 0 1-.537-.31v-1.4h-8.1a.358.358 0 0 1-.358-.358v-3.36Z"/>
+ </SvgIcon>
+ )
+}
+
export const PaperClipIcon = (props) => {
return(
<SvgIcon {...props} viewBox="0 0 12.208 25.75">
@@ -332,3 +356,27 @@
</SvgIcon>
);
};
+export const RoundSaltireIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 16 16">
+ <path d="M8 16a8 8 0 1 1 8-8 8.009 8.009 0 0 1-8 8ZM8 .888A7.112 7.112 0 1 0 15.112 8 7.12 7.12 0 0 0 8 .888Z"/>
+ <path d="M10.837 5.167a.444.444 0 0 0-.628 0l-2.2 2.2-2.214-2.2a.44406306.44406306 0 0 0-.628.628l2.2 2.2-2.2 2.2a.44904009.44904009 0 0 0 .628.642l2.2-2.2 2.2 2.2a.4507918.4507918 0 1 0 .642-.633l-2.2-2.2 2.2-2.209a.445.445 0 0 0 0-.628Z"/>
+ </SvgIcon>
+ )
+}
+
+export const TrashBinIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 15.44 16">
+ <path d="M17.2 4.08h-4.72v-.32A1.776 1.776 0 0 0 10.72 2H9.44a1.776 1.776 0 0 0-1.76 1.76V4H2.96a.56.56 0 1 0 0 1.12h1.12l.56 11.04A1.879 1.879 0 0 0 6.56 18h7.12a1.879 1.879 0 0 0 1.92-1.84l.56-11.04h1.12a.547.547 0 0 0 .56-.56c0-.32-.32-.48-.64-.48Zm-8.48 0v-.32a.631.631 0 0 1 .64-.64h1.28a.631.631 0 0 1 .64.64V4H8.72Zm6.24 1.04-.56 10.96a.8.8 0 0 1-.8.8H6.56a.756.756 0 0 1-.8-.8L5.12 5.12Z" transform="translate(-2.4 -2)"/>
+ </SvgIcon>
+ )
+}
+
+export const TwoSheetsIcon = (props) => {
+ return (
+ <SvgIcon {...props} viewBox="0 0 16 15.68">
+ <path d="M16.72 2.2H7.2a1.264 1.264 0 0 0-1.28 1.28V5.8H3.28A1.264 1.264 0 0 0 2 7.08v9.52a1.264 1.264 0 0 0 1.28 1.28h9.52a1.264 1.264 0 0 0 1.28-1.28v-2.32h2.64A1.264 1.264 0 0 0 18 13V3.48a1.264 1.264 0 0 0-1.28-1.28Zm-3.68 14.4a.212.212 0 0 1-.24.24H3.28a.212.212 0 0 1-.24-.24V7.08a.255.255 0 0 1 .24-.24h9.52a.212.212 0 0 1 .24.24Zm3.92-3.6a.212.212 0 0 1-.24.24h-2.64V7.08A1.264 1.264 0 0 0 12.8 5.8H6.96V3.48a.212.212 0 0 1 .24-.24h9.52a.212.212 0 0 1 .24.24V13Z" transform="translate(-2 -2.2)"/>
+ </SvgIcon>
+ )
+}