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:
parent
4852d90546
commit
77dfd154b2
|
@ -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 {
|
||||
|
|
|
@ -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)`
|
||||
& > span {
|
||||
word-break: break-all;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -70,6 +70,7 @@ const Name = styled.p`
|
|||
font-weight: 500;
|
||||
text-align: left;
|
||||
color: ${({ theme }) => theme.primary};
|
||||
white-space: nowrap;
|
||||
|
||||
${textMediumStyles}
|
||||
`;
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -37,7 +37,7 @@ export function ContactMenu({ id, setShowMenu }: ContactMenuProps) {
|
|||
|
||||
if (!contact) return null;
|
||||
return (
|
||||
<ContactDropdown>
|
||||
<ContactDropdown closeMenu={setShowMenu}>
|
||||
<ContactInfo>
|
||||
<UserIcon />
|
||||
<UserNameWrapper>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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};
|
||||
|
||||
&.active {
|
||||
fill: ${({ theme }) => theme.primary};
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -30,7 +30,7 @@ export function Member({
|
|||
id: contact.id,
|
||||
name: contact.customName ?? contact.trueName,
|
||||
type: "dm",
|
||||
description: "DM",
|
||||
description: "Contact",
|
||||
members: [contact],
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
{onlineContacts.length > 0 && (
|
||||
<MemberCategory>
|
||||
<MemberCategoryName>Online</MemberCategoryName>
|
||||
{Object.values(contacts)
|
||||
.filter((e) => e.id != bufToHex(identity.publicKey))
|
||||
.filter((e) => e.online)
|
||||
.map((contact) => (
|
||||
{onlineContacts.map((contact) => (
|
||||
<Member
|
||||
key={contact.id}
|
||||
contact={contact}
|
||||
|
@ -42,12 +55,11 @@ export function MembersList({ switchShowMembers }: MembersListProps) {
|
|||
/>
|
||||
))}
|
||||
</MemberCategory>
|
||||
)}
|
||||
{offlineContacts.length > 0 && (
|
||||
<MemberCategory>
|
||||
<MemberCategoryName>Offline</MemberCategoryName>
|
||||
{Object.values(contacts)
|
||||
.filter((e) => e.id != bufToHex(identity.publicKey))
|
||||
.filter((e) => !e.online)
|
||||
.map((contact) => (
|
||||
{offlineContacts.map((contact) => (
|
||||
<Member
|
||||
key={contact.id}
|
||||
contact={contact}
|
||||
|
@ -56,6 +68,7 @@ export function MembersList({ switchShowMembers }: MembersListProps) {
|
|||
/>
|
||||
))}
|
||||
</MemberCategory>
|
||||
)}
|
||||
</MembersListWrap>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -28,5 +28,5 @@ export const useContextMenu = (elementId: string) => {
|
|||
};
|
||||
});
|
||||
|
||||
return { showMenu };
|
||||
return { showMenu, setShowMenu };
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue