UI fixes for conversation details screen

This commit is contained in:
Josh Perez 2021-02-01 17:57:42 -05:00 committed by GitHub
parent ddebbf8121
commit 267ae80442
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 51 deletions

View file

@ -0,0 +1,4 @@
<svg id="Export" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<rect x="12.12" y="20" width="0.58" height="1.5"/>
<path d="M21.38,12A9.45,9.45,0,0,1,12,21.5,9.5,9.5,0,1,1,18.84,5.28l1,1-.35-1.3V2.56H21V8.5H15V7h2.39l1.42.38-1-1A8,8,0,0,0,4.12,12a7.88,7.88,0,1,0,15.76,0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -2924,6 +2924,12 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
.module-conversation-header__title-clickable { .module-conversation-header__title-clickable {
cursor: pointer; cursor: pointer;
&:focus {
@include mouse-mode {
outline: none;
}
}
} }
.module-conversation-header__note-to-self { .module-conversation-header__note-to-self {
@ -3189,7 +3195,7 @@ button.module-conversation-details__action-button {
} }
&__title { &__title {
@include font-body-1-bold; @include font-title-1;
padding-top: 12px; padding-top: 12px;
padding-bottom: 8px; padding-bottom: 8px;
} }
@ -3205,6 +3211,14 @@ button.module-conversation-details__action-button {
} }
} }
&__leave-group {
color: $color-accent-red;
}
&__block-group {
color: $color-accent-red;
}
&__tabs { &__tabs {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@ -3342,8 +3356,7 @@ button.module-conversation-details__action-button {
&--reset { &--reset {
&::after { &::after {
transform: scaleX(-1); -webkit-mask: url(../images/icons/v2/refresh-24.svg) no-repeat center;
-webkit-mask: url(../images/icons/v2/undo-24.svg) no-repeat center;
@include light-theme { @include light-theme {
background-color: $color-gray-75; background-color: $color-gray-75;
@ -3430,6 +3443,7 @@ button.module-conversation-details__action-button {
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: 0 20px; padding: 0 20px;
padding-bottom: 24px;
.module-media-grid-item { .module-media-grid-item {
border-radius: 4px; border-radius: 4px;
@ -3458,6 +3472,13 @@ button.module-conversation-details__action-button {
background: none; background: none;
border: none; border: none;
padding: 0; padding: 0;
@include light-theme {
color: $color-gray-95;
}
@include dark-theme {
color: $color-gray-05;
}
} }
} }

View file

@ -487,13 +487,15 @@ export class ConversationHeader extends React.Component<PropsType> {
private renderHeader(): JSX.Element { private renderHeader(): JSX.Element {
const { const {
conversationTitle, conversationTitle,
groupVersion,
id, id,
isMe, isMe,
onShowContactModal, onShowContactModal,
onShowConversationDetails,
type, type,
} = this.props; } = this.props;
if (conversationTitle) { if (conversationTitle !== undefined) {
return ( return (
<div className="module-conversation-header__title-flex"> <div className="module-conversation-header__title-flex">
<div className="module-conversation-header__title"> <div className="module-conversation-header__title">
@ -503,6 +505,35 @@ export class ConversationHeader extends React.Component<PropsType> {
); );
} }
const hasGV2AdminEnabled =
groupVersion === 2 &&
window.Signal.RemoteConfig.isEnabled('desktop.gv2Admin');
if (type === 'group' && hasGV2AdminEnabled) {
const onHeaderClick = () => onShowConversationDetails();
const onKeyDown = (e: React.KeyboardEvent): void => {
if (e.key === 'Enter' || e.key === ' ') {
e.stopPropagation();
e.preventDefault();
onShowConversationDetails();
}
};
return (
<div
className="module-conversation-header__title-flex module-conversation-header__title-clickable"
onClick={onHeaderClick}
onKeyDown={onKeyDown}
role="button"
tabIndex={0}
>
{this.renderAvatar()}
{this.renderTitle()}
</div>
);
}
if (type === 'group' || isMe) { if (type === 'group' || isMe) {
return ( return (
<div className="module-conversation-header__title-flex"> <div className="module-conversation-header__title-flex">

View file

@ -37,7 +37,11 @@ export const ConversationDetailsActions: React.ComponentType<Props> = ({
icon="leave" icon="leave"
/> />
} }
label={i18n('ConversationDetailsActions--leave-group')} label={
<div className="module-conversation-details__leave-group">
{i18n('ConversationDetailsActions--leave-group')}
</div>
}
/> />
<PanelRow <PanelRow
onClick={() => setConfirmingBlock(true)} onClick={() => setConfirmingBlock(true)}
@ -47,7 +51,11 @@ export const ConversationDetailsActions: React.ComponentType<Props> = ({
icon="block" icon="block"
/> />
} }
label={i18n('ConversationDetailsActions--block-group')} label={
<div className="module-conversation-details__block-group">
{i18n('ConversationDetailsActions--block-group')}
</div>
}
/> />
</PanelSection> </PanelSection>

View file

@ -45,16 +45,32 @@ const createProps = (overrideProps: Partial<Props>): Props => ({
memberships: overrideProps.memberships || [], memberships: overrideProps.memberships || [],
}); });
story.add('Basic', () => { story.add('Few', () => {
const memberships = createMemberships(10); const memberships = createMemberships(3);
const props = createProps({ memberships }); const props = createProps({ memberships });
return <ConversationDetailsMembershipList {...props} />; return <ConversationDetailsMembershipList {...props} />;
}); });
story.add('Few', () => { story.add('Limit', () => {
const memberships = createMemberships(3); const memberships = createMemberships(5);
const props = createProps({ memberships });
return <ConversationDetailsMembershipList {...props} />;
});
story.add('Limit +1', () => {
const memberships = createMemberships(6);
const props = createProps({ memberships });
return <ConversationDetailsMembershipList {...props} />;
});
story.add('Limit +2', () => {
const memberships = createMemberships(7);
const props = createProps({ memberships }); const props = createProps({ memberships });

View file

@ -24,7 +24,7 @@ export type Props = {
i18n: LocalizerType; i18n: LocalizerType;
}; };
const INITIAL_MEMBER_COUNT = 5; const MAX_MEMBER_COUNT = 5;
export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({ export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({
memberships, memberships,
@ -33,44 +33,47 @@ export const ConversationDetailsMembershipList: React.ComponentType<Props> = ({
}) => { }) => {
const [showAllMembers, setShowAllMembers] = React.useState<boolean>(false); const [showAllMembers, setShowAllMembers] = React.useState<boolean>(false);
const shouldHideRestMembers = memberships.length - MAX_MEMBER_COUNT > 1;
const membersToShow =
shouldHideRestMembers && !showAllMembers
? MAX_MEMBER_COUNT
: memberships.length;
return ( return (
<PanelSection <PanelSection
title={i18n('ConversationDetailsMembershipList--title', [ title={i18n('ConversationDetailsMembershipList--title', [
memberships.length.toString(), memberships.length.toString(),
])} ])}
> >
{memberships {memberships.slice(0, membersToShow).map(({ isAdmin, member }) => (
.slice(0, showAllMembers ? undefined : INITIAL_MEMBER_COUNT) <PanelRow
.map(({ isAdmin, member }) => ( key={member.id}
<PanelRow onClick={() => showContactModal(member.id)}
key={member.id} icon={
onClick={() => showContactModal(member.id)} <Avatar
icon={ conversationType="direct"
<Avatar i18n={i18n}
conversationType="direct" size={32}
i18n={i18n} {...member}
size={32} />
{...member} }
/> label={member.title}
} right={isAdmin ? i18n('GroupV2--admin') : ''}
label={member.title} />
right={isAdmin ? i18n('GroupV2--admin') : ''} ))}
/> {showAllMembers === false && shouldHideRestMembers && (
))} <PanelRow
{showAllMembers === false && className="module-conversation-details-membership-list--show-all"
memberships.length > INITIAL_MEMBER_COUNT && ( icon={
<PanelRow <ConversationDetailsIcon
className="module-conversation-details-membership-list--show-all" ariaLabel={i18n('ConversationDetailsMembershipList--show-all')}
icon={ icon="down"
<ConversationDetailsIcon />
ariaLabel={i18n('ConversationDetailsMembershipList--show-all')} }
icon="down" onClick={() => setShowAllMembers(true)}
/> label={i18n('ConversationDetailsMembershipList--show-all')}
} />
onClick={() => setShowAllMembers(true)} )}
label={i18n('ConversationDetailsMembershipList--show-all')}
/>
)}
</PanelSection> </PanelSection>
); );
}; };

View file

@ -19,7 +19,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
icon: boolean('with icon', overrideProps.icon !== undefined) ? ( icon: boolean('with icon', overrideProps.icon !== undefined) ? (
<ConversationDetailsIcon ariaLabel="timer" icon="timer" /> <ConversationDetailsIcon ariaLabel="timer" icon="timer" />
) : null, ) : null,
label: text('label', overrideProps.label || ''), label: text('label', (overrideProps.label as string) || ''),
info: text('info', overrideProps.info || ''), info: text('info', overrideProps.info || ''),
right: text('right', (overrideProps.right as string) || ''), right: text('right', (overrideProps.right as string) || ''),
actions: boolean('with action', overrideProps.actions !== undefined) ? ( actions: boolean('with action', overrideProps.actions !== undefined) ? (

View file

@ -9,7 +9,7 @@ export type Props = {
alwaysShowActions?: boolean; alwaysShowActions?: boolean;
className?: string; className?: string;
icon?: React.ReactNode; icon?: React.ReactNode;
label: string; label: string | React.ReactNode;
info?: string; info?: string;
right?: string | React.ReactNode; right?: string | React.ReactNode;
actions?: React.ReactNode; actions?: React.ReactNode;
@ -30,15 +30,15 @@ export const PanelRow: React.ComponentType<Props> = ({
}) => { }) => {
const content = ( const content = (
<> <>
{icon && <div className={bem('icon')}>{icon}</div>} {icon !== undefined ? <div className={bem('icon')}>{icon}</div> : null}
<div className={bem('label')}> <div className={bem('label')}>
<div>{label}</div> <div>{label}</div>
{info && <div className={bem('info')}>{info}</div>} {info !== undefined ? <div className={bem('info')}>{info}</div> : null}
</div> </div>
{right && <div className={bem('right')}>{right}</div>} {right !== undefined ? <div className={bem('right')}>{right}</div> : null}
{actions && ( {actions !== undefined ? (
<div className={alwaysShowActions ? '' : bem('actions')}>{actions}</div> <div className={alwaysShowActions ? '' : bem('actions')}>{actions}</div>
)} ) : null}
</> </>
); );

View file

@ -274,7 +274,7 @@ export function buildGroupLink(conversation: ConversationModel): string {
const bytes = proto.toArrayBuffer(); const bytes = proto.toArrayBuffer();
const hash = toWebSafeBase64(window.Signal.Crypto.arrayBufferToBase64(bytes)); const hash = toWebSafeBase64(window.Signal.Crypto.arrayBufferToBase64(bytes));
return `sgnl://signal.group/#${hash}`; return `https://signal.group/#${hash}`;
} }
export function parseGroupLink( export function parseGroupLink(