UI improvements (#135)

* Fix channel name styles

* Add active class to group icon

* Fix channel description

* Hide member category

* Fix community name alignment

* Add unmuting

* Add group unmuting

* Add user name to empty text

* Fix group description

* Add closing dropdown on click outside

* Add memoization

* Change useClickOutside
This commit is contained in:
Maria Rushkova 2021-11-26 10:23:52 +01:00 committed by GitHub
parent 4852d90546
commit 77dfd154b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 105 additions and 38 deletions

View File

@ -1,6 +1,7 @@
import React from "react";
import styled from "styled-components";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { useNarrow } from "../../contexts/narrowProvider";
import { ChannelData } from "../../models/ChannelData";
import { GroupIcon } from "../Icons/GroupIcon";
@ -14,12 +15,13 @@ function RenderChannelName({
channel: ChannelData;
className?: string;
}) {
const { activeChannel } = useMessengerContext();
switch (channel.type) {
case "group":
return (
<div className={className}>
<GroupIcon />
{channel.name}
<GroupIcon activeView={channel.id === activeChannel.id} />
{` ${channel.name}`}
</div>
);
case "channel":
@ -77,7 +79,7 @@ export function Channel({
<ChannelTextInfo>
<ChannelName
channel={channel}
active={isActive || narrow}
active={isActive || activeView || narrow}
muted={channel?.isMuted}
notified={notified}
/>
@ -89,7 +91,7 @@ export function Channel({
{!activeView && !!mention && mention > 0 && !channel?.isMuted && (
<NotificationBagde>{mention}</NotificationBagde>
)}
{channel?.isMuted && <MutedIcon />}
{channel?.isMuted && !activeView && <MutedIcon />}
</ChannelWrapper>
);
}
@ -101,6 +103,7 @@ const ChannelWrapper = styled.div<{ isNarrow?: boolean }>`
padding: 8px;
cursor: pointer;
width: ${({ isNarrow }) => (isNarrow ? "calc(100% - 162px)" : "100%")};
&.active {
background-color: ${({ theme }) => theme.activeChannelBackground};
border-radius: 8px;
@ -136,7 +139,7 @@ export const ChannelLogo = styled.div<{ icon?: string }>`
background-color: ${({ theme }) => theme.iconColor};
background-size: cover;
background-repeat: no-repeat;
backgroundimage: ${({ icon }) => icon && `url(${icon}`};
background-image: ${({ icon }) => icon && `url(${icon}`};
color: ${({ theme }) => theme.iconTextColor};
&.active {

View File

@ -1,6 +1,8 @@
import React from "react";
import { utils } from "status-communities/dist/cjs";
import styled from "styled-components";
import { useIdentity } from "../../contexts/identityProvider";
import { ChannelData } from "../../models/ChannelData";
import { textMediumStyles } from "../Text";
@ -12,6 +14,7 @@ type EmptyChannelProps = {
export function EmptyChannel({ channel }: EmptyChannelProps) {
const groupName = channel.name.split(", ");
const identity = useIdentity();
return (
<Wrapper>
@ -31,7 +34,7 @@ export function EmptyChannel({ channel }: EmptyChannelProps) {
</EmptyText>
) : channel.type === "group" ? (
<EmptyTextGroup>
You created a group with{" "}
<span>{utils.bufToHex(identity.publicKey)}</span> created a group with{" "}
<span>{groupName.slice(groupName.length - 1)}</span> and{" "}
<span>{groupName.at(-1)}</span>
</EmptyTextGroup>
@ -88,5 +91,7 @@ const EmptyText = styled.p`
`;
const EmptyTextGroup = styled(EmptyText)`
word-break: break-all;
& > span {
word-break: break-all;
}
`;

View File

@ -45,6 +45,7 @@ export function ChatCreation({ editGroup }: ChatCreationProps) {
id: group.join(""),
name: group.join(", "),
type: "group",
description: `${group.length + 1} members`,
})
: setChannel({
id: group[0],

View File

@ -124,11 +124,11 @@ const MessageImage = styled.img`
height: 100%;
object-fit: cover;
border-radius: 16px;
cursor; pointer;
cursor: pointer;
`;
const PreviewSiteNameWrapper = styled.div`
font-family: Inter;
font-family: "Inter";
font-style: normal;
font-weight: normal;
font-size: 12px;

View File

@ -70,6 +70,7 @@ const Name = styled.p`
font-weight: 500;
text-align: left;
color: ${({ theme }) => theme.primary};
white-space: nowrap;
${textMediumStyles}
`;

View File

@ -31,8 +31,9 @@ export const ChannelMenu = ({
const narrow = useNarrow();
const { clearNotifications, removeChannel } = useMessengerContext();
const { setModal } = useModal(EditModalName);
return (
<DropdownMenu>
<DropdownMenu closeMenu={setShowChannelMenu}>
{narrow && (
<MenuItem
onClick={() => {
@ -63,12 +64,15 @@ export const ChannelMenu = ({
)}
<MenuItem
onClick={() => {
channel.isMuted = true;
channel.isMuted = !channel.isMuted;
setShowChannelMenu(false);
}}
>
<MuteSvg width={16} height={16} />
<MenuText>Mute Chat</MenuText>
<MenuText>
{(channel.isMuted ? "Unmute" : "Mute") +
(channel.type === "group" ? " Group" : " Chat")}
</MenuText>
</MenuItem>
<MenuItem onClick={() => clearNotifications(channel.id)}>
<CheckSvg width={16} height={16} />

View File

@ -37,7 +37,7 @@ export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
if (!contact) return null;
return (
<ContactDropdown>
<ContactDropdown closeMenu={setShowMenu}>
<ContactInfo>
<UserIcon />
<UserNameWrapper>

View File

@ -1,16 +1,25 @@
import React, { ReactNode } from "react";
import React, { ReactNode, useRef } from "react";
import styled from "styled-components";
import { useClickOutside } from "../../hooks/useClickOutside";
import { textSmallStyles } from "../Text";
type DropdownMenuProps = {
children: ReactNode;
className?: string;
closeMenu: (val: boolean) => void;
};
export function DropdownMenu({ children, className }: DropdownMenuProps) {
export function DropdownMenu({
children,
className,
closeMenu,
}: DropdownMenuProps) {
const ref = useRef(null);
useClickOutside(ref, () => closeMenu(false));
return (
<MenuBlock className={className}>
<MenuBlock className={className} ref={ref}>
<MenuList>{children}</MenuList>
</MenuBlock>
);

View File

@ -14,10 +14,10 @@ interface ImageMenuProps {
}
export const ImageMenu = ({ imageId }: ImageMenuProps) => {
const { showMenu } = useContextMenu(imageId);
const { showMenu, setShowMenu } = useContextMenu(imageId);
return showMenu ? (
<ImageDropdown>
<ImageDropdown closeMenu={setShowMenu}>
<MenuItem onClick={() => copyImg(imageId)}>
<CopySvg height={16} width={16} /> <MenuText>Copy image</MenuText>
</MenuItem>

View File

@ -1,7 +1,11 @@
import React from "react";
import styled from "styled-components";
export const GroupIcon = () => {
interface GroupIconProps {
activeView?: boolean;
}
export const GroupIcon = ({ activeView }: GroupIconProps) => {
return (
<Icon
width="14"
@ -9,6 +13,7 @@ export const GroupIcon = () => {
viewBox="0 0 14 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`${activeView && "active"}`}
>
<path d="M7 4.5C8.24265 4.5 9.25 3.49264 9.25 2.25C9.25 1.00736 8.24265 0 7 0C5.75736 0 4.75 1.00736 4.75 2.25C4.75 3.49264 5.75736 4.5 7 4.5Z" />
<path d="M3.12343 9.01012C3.56395 7.27976 5.13252 6 7 6C8.86749 6 10.4361 7.27976 10.8766 9.01012C11.0128 9.54533 10.5523 10 10 10H4C3.44772 10 2.98718 9.54533 3.12343 9.01012Z" />
@ -19,7 +24,9 @@ export const GroupIcon = () => {
};
const Icon = styled.svg`
& > path {
fill: ${({ theme }) => theme.secondary};
fill: ${({ theme }) => theme.secondary};
&.active {
fill: ${({ theme }) => theme.primary};
}
`;

View File

@ -30,7 +30,7 @@ export function Member({
id: contact.id,
name: contact.customName ?? contact.trueName,
type: "dm",
description: "DM",
description: "Contact",
members: [contact],
});
};

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useMemo } from "react";
import { utils } from "status-communities/dist/cjs";
import { bufToHex } from "status-communities/dist/cjs/utils";
import styled from "styled-components";
@ -16,6 +16,21 @@ interface MembersListProps {
export function MembersList({ switchShowMembers }: MembersListProps) {
const { contacts } = useMessengerContext();
const identity = useIdentity();
const userContacts = useMemo(
() =>
Object.values(contacts).filter(
(e) => e.id != bufToHex(identity.publicKey)
),
[contacts, identity]
);
const onlineContacts = useMemo(
() => userContacts.filter((e) => e.online),
[userContacts]
);
const offlineContacts = useMemo(
() => userContacts.filter((e) => !e.online),
[userContacts]
);
return (
<MembersListWrap>
@ -28,12 +43,10 @@ export function MembersList({ switchShowMembers }: MembersListProps) {
<MemberName>{utils.bufToHex(identity.publicKey)}</MemberName>
</MemberData>
</MemberCategory>
<MemberCategory>
<MemberCategoryName>Online</MemberCategoryName>
{Object.values(contacts)
.filter((e) => e.id != bufToHex(identity.publicKey))
.filter((e) => e.online)
.map((contact) => (
{onlineContacts.length > 0 && (
<MemberCategory>
<MemberCategoryName>Online</MemberCategoryName>
{onlineContacts.map((contact) => (
<Member
key={contact.id}
contact={contact}
@ -41,13 +54,12 @@ export function MembersList({ switchShowMembers }: MembersListProps) {
switchShowMembers={switchShowMembers}
/>
))}
</MemberCategory>
<MemberCategory>
<MemberCategoryName>Offline</MemberCategoryName>
{Object.values(contacts)
.filter((e) => e.id != bufToHex(identity.publicKey))
.filter((e) => !e.online)
.map((contact) => (
</MemberCategory>
)}
{offlineContacts.length > 0 && (
<MemberCategory>
<MemberCategoryName>Offline</MemberCategoryName>
{offlineContacts.map((contact) => (
<Member
key={contact.id}
contact={contact}
@ -55,7 +67,8 @@ export function MembersList({ switchShowMembers }: MembersListProps) {
switchShowMembers={switchShowMembers}
/>
))}
</MemberCategory>
</MemberCategory>
)}
</MembersListWrap>
);
}

View File

@ -0,0 +1,24 @@
import { RefObject, useCallback, useEffect } from "react";
export const useClickOutside = (
ref: RefObject<HTMLDivElement>,
callback: () => void,
deps?: any[]
) => {
const handleClick = useCallback(
(e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as HTMLInputElement)) {
callback();
}
},
[ref, callback]
);
useEffect(() => {
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("mousedown", handleClick);
};
}, deps);
};

View File

@ -28,5 +28,5 @@ export const useContextMenu = (elementId: string) => {
};
});
return { showMenu };
return { showMenu, setShowMenu };
};