UI fixes for conversation details screen
This commit is contained in:
parent
ddebbf8121
commit
267ae80442
9 changed files with 134 additions and 51 deletions
4
images/icons/v2/refresh-24.svg
Normal file
4
images/icons/v2/refresh-24.svg
Normal 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 |
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) ? (
|
||||||
|
|
|
@ -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}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue