UI: Design edits (#5668)

Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
Alexander Zinchuk 2025-03-21 14:02:06 +04:00
parent e31a36c41d
commit 2e39a15660
49 changed files with 247 additions and 143 deletions

View File

@ -15,7 +15,7 @@
// Prevent loading additional 10 kB of icomoon font on initial load.
.css-icon-down {
position: absolute;
top: 1.125rem;
top: 1rem;
right: 1rem;
width: 0.75rem;
height: 0.75rem;
@ -29,7 +29,7 @@
&.open {
border-color: var(--color-primary);
transform: scaleY(-1) rotate(45deg);
top: 1.5rem;
top: 1.3125rem;
}
}
}

View File

@ -10,6 +10,7 @@ import { ANIMATION_END_DELAY } from '../../config';
import buildClassName from '../../util/buildClassName';
import { isoToEmoji } from '../../util/emoji/emoji';
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
import { IS_EMOJI_SUPPORTED } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText';
import useLang from '../../hooks/useLang';
@ -104,7 +105,9 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
handleTrigger();
};
const inputValue = filter ?? (value?.name || value?.defaultName || '');
const emoji = value && IS_EMOJI_SUPPORTED && renderText(isoToEmoji(value.iso2), ['hq_emoji']);
const name = value?.name || value?.defaultName || '';
const inputValue = filter ?? [emoji, name].filter(Boolean).join(' ');
return (
<div className={buildClassName('input-group', value && 'touched')}>

View File

@ -537,6 +537,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
<p>{lang(isEndGroupCallModal ? 'VoipGroupEndAlertText' : 'VoipGroupLeaveAlertText')}</p>
{!isEndGroupCallModal && (
<Checkbox
className="dialog-checkbox"
label={lang('VoipGroupEndChat')}
checked={shouldEndGroupCall}
onCheck={setShouldEndGroupCall}

View File

@ -18,7 +18,10 @@
}
.title {
font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
padding-left: 1rem;
}
.share {
font-weight: var(--font-weight-semibold);
}

View File

@ -115,6 +115,7 @@ const InviteLink: FC<OwnProps> = ({
size="smaller"
disabled={isDisabled}
onClick={handleShare}
className={styles.share}
>
{lang('FolderLinkScreen.LinkActionShare')}
</Button>

View File

@ -10,7 +10,6 @@
.header {
font-size: 1rem;
color: var(--color-text-secondary);
margin-bottom: 2rem;
position: relative;
&[dir="rtl"] {
@ -22,7 +21,6 @@
font-size: 0.875rem;
color: var(--color-text-secondary);
margin-top: 1rem;
margin-bottom: 0;
padding-top: 0.5rem;
padding-bottom: 1.5rem;

View File

@ -38,13 +38,14 @@
}
.settings-content {
border-top: 1px solid var(--color-borders);
border-top: 1px solid transparent;
transition: border-top-color 250ms ease-in-out;
height: calc(100% - var(--header-height));
overflow-y: scroll;
&.no-border, &.two-fa, &.local-passcode, &.password-form {
border-top: none;
&.scrolled {
border-top-color: var(--color-borders);
}
&.password-form .input-group.error label::first-letter {
@ -83,11 +84,9 @@
display: flex;
flex-direction: column;
align-items: center;
padding: 0 1.5rem 1rem;
padding: 0 1.5rem;
text-align: center;
@include mixins.side-panel-section;
&.no-border {
margin-bottom: 0;
box-shadow: none;
@ -125,7 +124,7 @@
.settings-item-simple,
.settings-item {
text-align: initial;
padding: 1.5rem 0.5rem 1rem;
padding: 0.5rem 0.5rem 1rem;
@include mixins.adapt-padding-to-scrollbar(0.5rem);
@include mixins.side-panel-section;
@ -152,10 +151,9 @@
&-description {
font-size: 0.875rem;
line-height: 1.3125;
color: var(--color-text-secondary);
margin-top: -0.5rem;
margin-bottom: 1rem;
padding-inline-start: 1rem;
margin: -0.5rem 1rem 1rem;
.settings-content.two-fa &,
.settings-content.password-form &,
@ -163,11 +161,6 @@
font-size: 1rem;
}
.settings-edit-profile & {
margin-bottom: 0;
padding-bottom: 1.5rem;
}
&[dir="rtl"] {
text-align: right;
unicode-bidi: plaintext;
@ -296,10 +289,13 @@
padding: 0.5rem 1rem 0 1rem;
}
.settings-group {
padding: 1rem 1.5rem;
}
.settings-fab-wrapper {
height: calc(100% - var(--header-height));
position: relative;
overflow: hidden;
.settings-content {
height: 100%;
@ -315,10 +311,6 @@
}
}
.settings-edit-profile {
padding: 0 1.5rem !important;
}
.settings-quick-reaction {
.Radio-main .label {
display: flex;

View File

@ -1,5 +1,5 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useState } from '../../../lib/teact/teact';
import React, { memo, useRef, useState } from '../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../global';
import type { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/useFoldersReducer';
@ -10,6 +10,7 @@ import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment';
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
import useLastCallback from '../../../hooks/useLastCallback';
import useMarkScrolled from '../../../hooks/useMarkScrolled/useMarkScrolled';
import Transition from '../../ui/Transition';
import SettingsFolders from './folders/SettingsFolders';
@ -161,9 +162,17 @@ const Settings: FC<OwnProps> = ({
}) => {
const { closeShareChatFolderModal } = getActions();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
const [twoFaState, twoFaDispatch] = useTwoFaReducer();
const [privacyPasscode, setPrivacyPasscode] = useState<string>('');
useMarkScrolled({
containerRef,
selector: '.settings-content',
}, [currentScreen]);
const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => {
const isFromSettings = selectTabState(getGlobal()).shareFolderScreen?.isFromSettings;
@ -506,6 +515,7 @@ const Settings: FC<OwnProps> = ({
return (
<Transition
ref={containerRef}
id="Settings"
name={shouldSkipTransition ? 'none' : LAYERS_ANIMATION_NAME}
activeKey={currentScreen}

View File

@ -211,34 +211,36 @@ const SettingsEditProfile: FC<OwnProps & StateProps> = ({
return (
<div className="settings-fab-wrapper">
<div className="settings-content no-border custom-scroll">
<div className="settings-edit-profile settings-item">
<AvatarEditable
currentAvatarBlobUrl={currentAvatarBlobUrl}
onChange={handlePhotoChange}
title="Edit your profile photo"
disabled={isLoading}
/>
<InputText
value={firstName}
onChange={handleFirstNameChange}
label={lang('FirstName')}
disabled={isLoading}
error={error === ERROR_FIRST_NAME_MISSING ? error : undefined}
/>
<InputText
value={lastName}
onChange={handleLastNameChange}
label={lang('LastName')}
disabled={isLoading}
/>
<TextArea
value={bio}
onChange={handleBioChange}
label={lang('UserBio')}
disabled={isLoading}
maxLength={maxBioLength}
maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined}
/>
<div className="settings-item">
<div className="settings-input">
<AvatarEditable
currentAvatarBlobUrl={currentAvatarBlobUrl}
onChange={handlePhotoChange}
title="Edit your profile photo"
disabled={isLoading}
/>
<InputText
value={firstName}
onChange={handleFirstNameChange}
label={lang('FirstName')}
disabled={isLoading}
error={error === ERROR_FIRST_NAME_MISSING ? error : undefined}
/>
<InputText
value={lastName}
onChange={handleLastNameChange}
label={lang('LastName')}
disabled={isLoading}
/>
<TextArea
value={bio}
onChange={handleBioChange}
label={lang('UserBio')}
disabled={isLoading}
maxLength={maxBioLength}
maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined}
/>
</div>
<p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
{renderText(lang('lng_settings_about_bio'), ['br', 'simple_markdown'])}

View File

@ -133,7 +133,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content custom-scroll">
<div className="settings-item pt-3">
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('Settings')}</h4>
<RangeSlider

View File

@ -122,7 +122,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
return (
<div className="SettingsGeneralBackground settings-content custom-scroll">
<div className="settings-item pt-3">
<div className="settings-item">
<ListItem
icon="camera-add"
className="mb-0"

View File

@ -67,7 +67,7 @@ const SettingsPasswordForm: FC<OwnProps> = ({
<PasswordMonkey isBig isPasswordVisible={shouldShowPassword} />
</div>
<div className="settings-item pt-2">
<div className="settings-item settings-group">
<PasswordForm
error={validationError || error}
hint={hint}

View File

@ -145,7 +145,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content custom-scroll">
<div className="settings-item pt-3">
<div className="settings-item">
<ListItem
icon="delete-user"
narrow

View File

@ -306,7 +306,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
</div>
{!isOnlyInvites && (
<div className="settings-item pt-3">
<div className="settings-item">
{state.error && state.error === ERROR_NO_CHATS && (
<p className="settings-item-description color-danger mb-2" dir={lang.isRtl ? 'rtl' : undefined}>
{lang(state.error)}

View File

@ -43,7 +43,7 @@ const SettingsPasscodeCongratulations: FC<OwnProps> = ({
</p>
</div>
<div className="settings-item pt-2">
<div className="settings-item settings-group">
<Button onClick={fullReset}>{lang('Back')}</Button>
</div>
</div>

View File

@ -41,7 +41,7 @@ const SettingsPasscodeEnabled: FC<OwnProps> = ({
</p>
</div>
<div className="settings-item pt-2">
<div className="settings-item">
<ListItem
icon="edit"
// eslint-disable-next-line react/jsx-no-bind

View File

@ -44,7 +44,7 @@ const SettingsPasscodeStart: FC<OwnProps> = ({
</p>
</div>
<div className="settings-item pt-2">
<div className="settings-item settings-group">
<Button onClick={onStart}>{lang('EnablePasscode')}</Button>
</div>
</div>

View File

@ -46,7 +46,7 @@ const SettingsTwoFaCongratulations: FC<OwnProps> = ({
</p>
</div>
<div className="settings-item pt-2">
<div className="settings-item settings-group">
<Button onClick={handleClick}>{lang('TwoStepVerificationPasswordReturnSettings')}</Button>
</div>
</div>

View File

@ -86,12 +86,14 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
<div className="settings-content two-fa custom-scroll">
<div className="settings-content-header no-border">
<AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} className="settings-content-icon" />
<p className="settings-item-description mb-3" dir="auto">
{lang('TwoStepAuth.ConfirmEmailDescription', recoveryEmail)}
</p>
{recoveryEmail && (
<p className="settings-item-description mb-3" dir="auto">
{lang('TwoStepAuth.ConfirmEmailDescription', recoveryEmail)}
</p>
)}
</div>
<div className="settings-item pt-2">
<div className="settings-item settings-group">
<InputText
value={value}
ref={inputRef}

View File

@ -45,7 +45,7 @@ const SettingsTwoFaEnabled: FC<OwnProps> = ({
</p>
</div>
<div className="settings-item pt-2">
<div className="settings-item">
<ListItem
icon="edit"
// eslint-disable-next-line react/jsx-no-bind

View File

@ -105,12 +105,14 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
<div className="settings-content two-fa custom-scroll">
<div className="settings-content-header no-border">
<AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} className="settings-content-icon" />
<p className="settings-item-description mb-3" dir="auto">
{lang('RecoveryEmailSubtitle')}
</p>
{type === 'email' && (
<p className="settings-item-description mb-3" dir="auto">
{lang('RecoveryEmailSubtitle')}
</p>
)}
</div>
<div className="settings-item pt-2">
<div className="settings-item settings-group">
<form action="" onSubmit={handleSubmit}>
<InputText
ref={inputRef}

View File

@ -42,7 +42,7 @@ const SettingsTwoFaStart: FC<OwnProps> = ({
</p>
</div>
<div className="settings-item pt-0">
<div className="settings-item settings-group">
<Button onClick={onStart}>{lang('EditAdminTransferSetPassword')}</Button>
</div>
</div>

View File

@ -8,6 +8,13 @@
overflow-x: hidden;
overflow-y: scroll;
border-top: 1px solid transparent;
transition: border-top-color 250ms ease-in-out;
&.scrolled {
border-top-color: var(--color-borders);
}
> .profile-info > .ChatInfo {
grid-area: chat_info;

View File

@ -32,6 +32,15 @@
overflow: hidden;
}
.panel-content {
border-top: 1px solid transparent;
transition: border-top-color 250ms ease-in-out;
&.scrolled {
border-top-color: var(--color-borders);
}
}
.Management .section > .ChatInfo {
padding: 0 1.5rem;
margin: 1rem 0;

View File

@ -1,5 +1,7 @@
import type { FC } from '../../lib/teact/teact';
import React, { memo, useEffect, useState } from '../../lib/teact/teact';
import React, {
memo, useEffect, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { ProfileTabType, ThreadId } from '../../types';
@ -22,6 +24,7 @@ import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useHistoryBack from '../../hooks/useHistoryBack';
import useLastCallback from '../../hooks/useLastCallback';
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
import useMarkScrolled from '../../hooks/useMarkScrolled/useMarkScrolled';
import useWindowSize from '../../hooks/window/useWindowSize';
import Transition from '../ui/Transition';
@ -106,6 +109,9 @@ const RightColumn: FC<OwnProps & StateProps> = ({
closeMonetizationStatistics,
} = getActions();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
const { width: windowWidth } = useWindowSize();
const [profileState, setProfileState] = useState<ProfileState>(
isSavedMessages && !isSavedDialog ? ProfileState.SavedDialogs : ProfileState.Profile,
@ -135,6 +141,11 @@ const RightColumn: FC<OwnProps & StateProps> = ({
const renderingContentKey = useCurrentOrPrev(contentKey, true, !isChatSelected) ?? -1;
useMarkScrolled({
containerRef,
selector: ':scope .custom-scroll, :scope .panel-content',
}, [contentKey, managementScreen, chatId, threadId]);
const close = useLastCallback((shouldScrollUp = true) => {
switch (contentKey) {
case RightColumnContent.AddingMembers:
@ -389,6 +400,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
onScreenSelect={setManagementScreen}
/>
<Transition
ref={containerRef}
name={(shouldSkipTransition || shouldSkipHistoryAnimations) ? 'none' : 'zoomFade'}
renderCount={MAIN_SCREENS_COUNT + MANAGEMENT_SCREENS_COUNT}
activeKey={isManagement ? MAIN_SCREENS_COUNT + managementScreen : renderingContentKey}

View File

@ -212,7 +212,7 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section">
<AvatarEditable
currentAvatarBlobUrl={currentAvatarBlobUrl}

View File

@ -111,7 +111,7 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section">
<ListItem
icon="recent"

View File

@ -22,6 +22,7 @@ import useOldLang from '../../../hooks/useOldLang';
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
import Icon from '../../common/icons/Icon';
import LinkField from '../../common/LinkField';
import ManageUsernames from '../../common/ManageUsernames';
import SafeLink from '../../common/SafeLink';
import UsernameInput from '../../common/UsernameInput';
@ -198,7 +199,7 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}>
<h3 className="section-heading">{lang(`${langPrefix2}Type`)}</h3>
<RadioGroup
@ -212,7 +213,7 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps> = ({
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}>
{privateInviteLink ? (
<>
<SafeLink url={privateInviteLink} className="group-link" text={privateInviteLink} />
<LinkField link={privateInviteLink} className="invite-link" />
<p className="section-info" dir={lang.isRtl ? 'rtl' : undefined}>
{lang(`${langPrefix1}PrivateLinkHelp`)}
</p>
@ -235,14 +236,16 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps> = ({
</div>
) : (
<div className="section no-border">
<UsernameInput
asLink
currentUsername={currentUsername}
isLoading={isLoading}
isUsernameAvailable={isUsernameAvailable}
checkedUsername={checkedUsername}
onChange={handleUsernameChange}
/>
<div className="settings-input">
<UsernameInput
asLink
currentUsername={currentUsername}
isLoading={isLoading}
isUsernameAvailable={isUsernameAvailable}
checkedUsername={checkedUsername}
onChange={handleUsernameChange}
/>
</div>
{error === USERNAME_PURCHASE_ERROR && renderPurchaseLink()}
<p className="section-info" dir="auto">
{lang(`${langPrefix2}.Username.CreatePublicLinkHelp`)}

View File

@ -83,7 +83,7 @@ const ManageChatRemovedUsers: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}>
<p className="section-help">{lang(isChannel ? 'NoBlockedChannel2' : 'NoBlockedGroup2')}</p>

View File

@ -246,7 +246,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section">
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.DiscussionGroups}

View File

@ -321,7 +321,7 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section">
<AvatarEditable
isForForum={isForumEnabled}

View File

@ -205,7 +205,7 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section">
<ListItem inactive className="chat-item-clickable">
<PrivateChatInfo

View File

@ -208,7 +208,7 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
{noAdmins && renderSearchField()}
<div className="custom-scroll">
<div className="panel-content custom-scroll">
{canHideParticipants && !isChannel && (
<div className="section">
<ListItem icon="group" ripple onClick={handleToggleParticipantsHidden}>

View File

@ -169,7 +169,7 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
style={`--shift-height: ${ITEMS_COUNT * ITEM_HEIGHT}px;`
+ `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT}px;`}
>
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section without-bottom-shadow">
<h3 className="section-heading" dir="auto">{lang('ChannelPermissionsHeader')}</h3>
<PermissionCheckboxList

View File

@ -158,7 +158,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
return (
<div className="Management ManageInvite">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section">
<Checkbox
label={lang('ApproveNewMembers')}
@ -177,7 +177,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
<p className="section-help hint">{lang('LinkNameHelp')}</p>
</div>
<div className="section">
<div className="section-header">{lang('LimitByPeriod')}</div>
<div className="section-heading">{lang('LimitByPeriod')}</div>
<RadioGroup
name="expireOptions"
options={[
@ -214,7 +214,7 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
</div>
{!isRequestNeeded && (
<div className="section">
<div className="section-header">{lang('LimitNumberOfUses')}</div>
<div className="section-heading">{lang('LimitNumberOfUses')}</div>
<RadioGroup
name="usageOptions"
options={[

View File

@ -1,20 +1,19 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
import React, { memo, useEffect } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiChatInviteImporter, ApiExportedInvite, ApiUser } from '../../../api/types';
import { isChatChannel } from '../../../global/helpers';
import { selectChat, selectTabState } from '../../../global/selectors';
import { copyTextToClipboard } from '../../../util/clipboard';
import { formatFullDate, formatMediaDateTime, formatTime } from '../../../util/dates/dateFormat';
import { getServerTime } from '../../../util/serverTime';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang';
import LinkField from '../../common/LinkField';
import PrivateChatInfo from '../../common/PrivateChatInfo';
import Button from '../../ui/Button';
import ListItem from '../../ui/ListItem';
import Spinner from '../../ui/Spinner';
@ -44,7 +43,6 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
onClose,
}) => {
const {
showNotification,
loadChatInviteImporters,
loadChatInviteRequesters,
openChat,
@ -64,13 +62,6 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
}
}, [chatId, link, loadChatInviteImporters, loadChatInviteRequesters]);
const handleCopyClicked = useCallback(() => {
copyTextToClipboard(invite!.link);
showNotification({
message: lang('LinkCopied'),
});
}, [invite, lang, showNotification]);
useHistoryBack({
isActive,
onBack: onClose,
@ -81,7 +72,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
if (!importers) return <Spinner />;
return (
<div className="section">
<p>{importers.length ? lang('PeopleJoined', usage) : lang('NoOneJoined')}</p>
<p className="section-heading">{importers.length ? lang('PeopleJoined', usage) : lang('NoOneJoined')}</p>
<p className="section-help">
{!importers.length && (
usageLimit ? lang('PeopleCanJoinViaLinkCount', usageLimit - usage) : lang('NoOneJoinedYet')
@ -114,7 +105,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
if (!requesters?.length) return undefined;
return (
<div className="section">
<p>{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</p>
<p className="section-heading">{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</p>
<p className="section-help">
{requesters.map((requester) => (
<ListItem
@ -136,21 +127,14 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
return (
<div className="Management ManageInviteInfo">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
{!invite && (
<p className="section-help">{lang('Loading')}</p>
)}
{invite && (
<>
<div className="section">
<h3 className="link-title">{invite.title || invite.link}</h3>
<input
className="form-control"
value={invite.link}
readOnly
onClick={handleCopyClicked}
/>
<Button className="copy-link" onClick={handleCopyClicked}>{lang('CopyLink')}</Button>
<LinkField title={invite.title} link={invite.link} className="invite-link" />
{Boolean(expireDate) && (
<p className="section-help">
{isExpired
@ -161,7 +145,7 @@ const ManageInviteInfo: FC<OwnProps & StateProps> = ({
</div>
{adminId && (
<div className="section">
<p>{lang('LinkCreatedeBy')}</p>
<p className="section-heading">{lang('LinkCreatedeBy')}</p>
<ListItem
className="chat-item-clickable scroll-item small-icon"
// eslint-disable-next-line react/jsx-no-bind

View File

@ -26,7 +26,6 @@ import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import Icon from '../../common/icons/Icon';
import LinkField from '../../common/LinkField';
import NothingFound from '../../common/NothingFound';
import Button from '../../ui/Button';
import ConfirmDialog from '../../ui/ConfirmDialog';
import ListItem, { type MenuItemContextAction } from '../../ui/ListItem';
@ -274,7 +273,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
return (
<div className="Management ManageInvites">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
<div className="section">
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.Invite}
@ -295,9 +294,9 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
</div>
)}
<div className="section" teactFastList>
<Button isText key="create" className="create-link" onClick={handleCreateNewClick}>
<ListItem icon="add" withPrimaryColor key="create" className="create-link" onClick={handleCreateNewClick}>
{oldLang('CreateNewLink')}
</Button>
</ListItem>
{(!temporalInvites || !temporalInvites.length) && <NothingFound text="No links found" key="nothing" />}
{temporalInvites?.map((invite) => (
<ListItem

View File

@ -191,7 +191,7 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
return (
<div className="Management">
<div className="custom-scroll">
<div className="panel-content custom-scroll">
{ localReactionsLimit && shouldShowReactionsLimit && (
<div className="section">
<h3 className="section-heading">

View File

@ -103,6 +103,10 @@
padding: 0 1rem;
}
.invite-link {
padding: 0 1rem;
}
.section-info_push {
margin-top: 0.25rem;
}
@ -124,6 +128,7 @@
.RangeSlider {
margin-top: 2rem;
margin-inline-start: 1rem;
margin-inline-end: 1rem;
}
.button-position {
@ -170,6 +175,10 @@
.ManageInvites {
.create-link {
margin-bottom: 0.5rem;
.icon-add {
margin-inline-start: 0.1875rem;
margin-inline-end: 1.1875rem;
}
}
.ListItem-button {
@ -209,6 +218,7 @@
.ManageInvite {
.link-name {
padding: 0 1rem;
margin-bottom: 1rem;
}

View File

@ -13,12 +13,13 @@
}
.section-header {
font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
}
.section {
padding: 0.625rem;
padding: 1.5rem;
padding-inline-end: calc(1.5rem - var(--scrollbar-width));
@include mixins.side-panel-section;
}
@ -110,10 +111,23 @@
}
.giveawayButton {
margin-bottom: 0.5rem;
margin: 0 -1rem 0.5rem;
}
.giveawayIcon {
width: 2.75rem;
height: 2.75rem;
}
.primaryLink {
position: relative;
margin: 0 1rem 0.5rem;
}
.copy {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translate(0, -50%);
z-index: 1;
}

View File

@ -279,7 +279,7 @@ const BoostStatistics = ({
}
return (
<div className={buildClassName(styles.root, 'custom-scroll')}>
<div className={buildClassName(styles.root, 'panel-content custom-scroll')}>
{!isLoaded && <Loading />}
{isLoaded && statsOverview && (
<>

View File

@ -3,6 +3,9 @@
overflow-x: hidden;
overflow-y: hidden;
border-top: 1px solid transparent;
transition: border-top-color 0.3s ease-in-out;
:global(.lovely-chart--container) {
font: inherit !important;
font-size: 13px !important;

View File

@ -189,17 +189,15 @@ const Statistics: FC<OwnProps & StateProps> = ({
graphs, graphTitles, isReady, statistics, lang, chatId, loadStatisticsAsyncGraph, dcId, forceUpdate,
]);
if (!isReady || !statistics) {
return <Loading />;
}
return (
<div className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}>
<StatisticsOverview
statistics={statistics}
type={isGroup ? 'group' : 'channel'}
title={lang('StatisticOverview')}
/>
<div className={buildClassName(styles.root, 'panel-content custom-scroll', isReady && styles.ready)}>
{statistics && (
<StatisticsOverview
statistics={statistics}
type={isGroup ? 'group' : 'channel'}
title={lang('StatisticOverview')}
/>
)}
{!loadedCharts.current.length && <Loading />}
@ -209,7 +207,7 @@ const Statistics: FC<OwnProps & StateProps> = ({
))}
</div>
{Boolean((statistics as ApiChannelStatistics).recentPosts?.length) && (
{Boolean((statistics as ApiChannelStatistics)?.recentPosts?.length) && (
<div className={styles.messages}>
<h2 className={styles.messagesTitle}>{lang('ChannelStats.Recent.Header')}</h2>

View File

@ -1,5 +1,5 @@
.root {
padding: 1rem 0.75rem;
padding: 1rem 1.5rem;
margin-bottom: 1rem;
border-bottom: 0.0625rem solid var(--color-borders);
}

View File

@ -185,6 +185,16 @@
}
}
&.primary {
.ListItem-button {
color: var(--color-primary);
.ListItem-main-icon {
color: inherit;
}
}
}
&-context-menu {
position: absolute;

View File

@ -60,6 +60,7 @@ interface OwnProps {
inactive?: boolean;
focus?: boolean;
destructive?: boolean;
withPrimaryColor?: boolean;
multiline?: boolean;
isStatic?: boolean;
allowSelection?: boolean;
@ -98,6 +99,7 @@ const ListItem: FC<OwnProps> = ({
inactive,
focus,
destructive,
withPrimaryColor,
multiline,
isStatic,
allowSelection,
@ -209,6 +211,7 @@ const ListItem: FC<OwnProps> = ({
contextMenuAnchor && 'has-menu-open',
focus && 'focus',
destructive && 'destructive',
withPrimaryColor && 'primary',
multiline && 'multiline',
isStatic && 'is-static',
withColorTransition && 'with-color-transition',

View File

@ -0,0 +1,38 @@
import type { RefObject } from '../../lib/teact/teact';
import { useEffect } from '../../lib/teact/teact';
import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { throttle } from '../../util/schedulers';
const THROTTLE_DELAY = 100;
const useMarkScrolled = ({
containerRef, selector,
}: {
containerRef: RefObject<HTMLDivElement | null>;
selector: string;
}, deps: unknown[]) => {
useEffect(() => {
const elements = containerRef?.current?.querySelectorAll(selector);
if (!elements?.length) return undefined;
const handleScroll = throttle((event: Event) => {
const target = event.target as HTMLElement;
const isScrolled = target.scrollTop > 0;
requestMutation(() => {
target.classList.toggle('scrolled', isScrolled);
});
}, THROTTLE_DELAY);
elements.forEach((el) => el.addEventListener('scroll', handleScroll, { passive: true }));
// Trigger the scroll handler immediately to apply the current state
elements.forEach((el) => el.dispatchEvent(new Event('scroll', { bubbles: false })));
return () => {
elements.forEach((el) => el.removeEventListener('scroll', handleScroll));
};
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps
}, [containerRef, selector, ...deps]);
};
export default useMarkScrolled;

View File

@ -17,7 +17,7 @@
&-title {
font-size: 16px;
float: left;
margin-right: 2em;
margin: 0 1rem;
text-transform: lowercase;
&:first-letter {

View File

@ -211,7 +211,7 @@ $color-message-story-mention-to: #74bcff;
--border-radius-forum-avatar: 33.3333%;
--messages-container-width: 45.5rem;
--right-column-width: 26.5rem;
--header-height: 3.5rem;
--header-height: 3.4375rem;
--custom-emoji-size: 1.25rem;
--emoji-size: 1.25rem;
--custom-emoji-border-radius: 0;