Settings: Redesign (#6922)

Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
This commit is contained in:
Alexander Zinchuk 2026-06-01 01:15:35 +02:00
parent c4b1bad481
commit 60f3995e82
105 changed files with 2216 additions and 1978 deletions

View File

@ -22,6 +22,7 @@ type OwnProps = {
isDisabled?: boolean; isDisabled?: boolean;
className?: string; className?: string;
withShare?: boolean; withShare?: boolean;
noTitle?: boolean;
onRevoke?: VoidFunction; onRevoke?: VoidFunction;
}; };
@ -31,6 +32,7 @@ const InviteLink: FC<OwnProps> = ({
isDisabled, isDisabled,
className, className,
withShare, withShare,
noTitle,
onRevoke, onRevoke,
}) => { }) => {
const lang = useLang(); const lang = useLang();
@ -76,9 +78,11 @@ const InviteLink: FC<OwnProps> = ({
return ( return (
<div className={className}> <div className={className}>
<p className={styles.title}> {!noTitle && (
{oldLang(title || 'InviteLink.InviteLink')} <p className={styles.title}>
</p> {oldLang(title || 'InviteLink.InviteLink')}
</p>
)}
<div className={styles.primaryLink}> <div className={styles.primaryLink}>
<input <input
className={buildClassName('form-control', styles.input)} className={buildClassName('form-control', styles.input)}

View File

@ -1,29 +1,5 @@
@use "../../styles/mixins";
.container { .container {
margin-bottom: 0.625rem; padding-inline: 1.5rem;
padding: 1.5rem 1.5rem 0;
@include mixins.side-panel-section;
}
.header {
position: relative;
font-size: 1rem;
color: var(--color-text-secondary);
&[dir="rtl"] {
text-align: right;
}
}
.description {
margin-bottom: 0;
padding-top: 0.5rem;
padding-bottom: 1.5rem;
font-size: 0.875rem;
color: var(--color-text-secondary);
} }
.sortableContainer { .sortableContainer {

View File

@ -14,6 +14,7 @@ import useLang from '../../hooks/useLang';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
import Island, { IslandDescription, IslandTitle } from '../gili/layout/Island';
import ConfirmDialog from '../ui/ConfirmDialog'; import ConfirmDialog from '../ui/ConfirmDialog';
import Draggable from '../ui/Draggable'; import Draggable from '../ui/Draggable';
import ListItem from '../ui/ListItem'; import ListItem from '../ui/ListItem';
@ -147,10 +148,10 @@ const ManageUsernames: FC<OwnProps> = ({
return ( return (
<> <>
<div className={styles.container}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<h4 className={styles.header} dir={lang.isRtl ? 'rtl' : undefined}> {oldLang('lng_usernames_subtitle')}
{oldLang('lng_usernames_subtitle')} </IslandTitle>
</h4> <Island className={styles.container}>
<div className={styles.sortableContainer} style={`height: ${(usernames.length) * USERNAME_HEIGHT_PX}px`}> <div className={styles.sortableContainer} style={`height: ${(usernames.length) * USERNAME_HEIGHT_PX}px`}>
{usernames.map((usernameData, i) => { {usernames.map((usernameData, i) => {
const isDragged = state.draggedIndex === i; const isDragged = state.draggedIndex === i;
@ -201,10 +202,10 @@ const ManageUsernames: FC<OwnProps> = ({
); );
})} })}
</div> </div>
<p className={styles.description} dir={lang.isRtl ? 'rtl' : undefined}> </Island>
{oldLang('lng_usernames_description')} <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
</p> {oldLang('lng_usernames_description')}
</div> </IslandDescription>
<ConfirmDialog <ConfirmDialog
isOpen={Boolean(usernameForConfirm)} isOpen={Boolean(usernameForConfirm)}
onClose={closeConfirmUsernameDialog} onClose={closeConfirmUsernameDialog}

View File

@ -21,6 +21,7 @@ import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import InfiniteScroll from '../../ui/InfiniteScroll'; import InfiniteScroll from '../../ui/InfiniteScroll';
import InputText from '../../ui/InputText'; import InputText from '../../ui/InputText';
@ -93,6 +94,7 @@ type OwnProps<CategoryType extends string> = {
withPeerTypes?: boolean; withPeerTypes?: boolean;
withPeerUsernames?: boolean; withPeerUsernames?: boolean;
withDefaultPadding?: boolean; withDefaultPadding?: boolean;
withIslands?: boolean;
onFilterChange?: (value: string) => void; onFilterChange?: (value: string) => void;
onDisabledClick?: (id: string, isSelected: boolean) => void; onDisabledClick?: (id: string, isSelected: boolean) => void;
onLoadMore?: () => void; onLoadMore?: () => void;
@ -125,6 +127,7 @@ const PeerPicker = <CategoryType extends string = CustomPeerType>({
withPeerTypes, withPeerTypes,
withPeerUsernames, withPeerUsernames,
withDefaultPadding, withDefaultPadding,
withIslands,
onFilterChange, onFilterChange,
onDisabledClick, onDisabledClick,
onLoadMore, onLoadMore,
@ -250,6 +253,19 @@ const PeerPicker = <CategoryType extends string = CustomPeerType>({
onFilterChange?.(value); onFilterChange?.(value);
}); });
function renderSearchInput() {
return (
<InputText
id={searchInputId}
ref={inputRef}
value={filterValue}
onChange={handleFilterChange}
placeholder={filterPlaceholder || oldLang('SelectChat')}
noMargin={withIslands}
/>
);
}
const [viewportIds, getMore] = useInfiniteScroll( const [viewportIds, getMore] = useInfiniteScroll(
onLoadMore, sortedItemIds, Boolean(filterValue), onLoadMore, sortedItemIds, Boolean(filterValue),
); );
@ -387,62 +403,78 @@ const PeerPicker = <CategoryType extends string = CustomPeerType>({
? sections.some((s) => s.ids.length > 0) ? sections.some((s) => s.ids.length > 0)
: Boolean(viewportIds?.length); : Boolean(viewportIds?.length);
const SearchWrapper = withIslands ? Island : 'div';
const ListWrapper = withIslands ? Island : 'div';
function renderListWrapper(content: TeactNode) {
return withIslands
? <ListWrapper className={styles.islandList}>{content}</ListWrapper>
: content;
}
return ( return (
<div className={buildClassName(styles.container, className)}> <div className={buildClassName(styles.container, className)}>
{isSearchable && ( {isSearchable && (
<div className={buildClassName(styles.header, 'custom-scroll')} dir={oldLang.isRtl ? 'rtl' : undefined}> <>
{selectedCategories?.map((category) => ( {(!withIslands || selectedIds.length > 0 || (selectedCategories && selectedCategories.length > 0)) && (
<PeerChip <SearchWrapper
className={styles.peerChip} className={buildClassName(withIslands ? styles.islandHeader : styles.header, 'custom-scroll')}
customPeer={categoriesByType[category]} dir={oldLang.isRtl ? 'rtl' : undefined}
onClick={handleItemClick} >
clickArg={category} {selectedCategories?.map((category) => (
canClose <PeerChip
/> className={styles.peerChip}
))} customPeer={categoriesByType[category]}
{lockedSelectedIds?.map((id, i) => ( onClick={handleItemClick}
<PeerChip clickArg={category}
className={styles.peerChip} canClose
peerId={id} />
isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT} ))}
forceShowSelf={forceShowSelf} {lockedSelectedIds?.map((id, i) => (
onClick={handleItemClick} <PeerChip
clickArg={id} className={styles.peerChip}
/> peerId={id}
))} isMinimized={shouldMinimize && i < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT}
{unlockedSelectedIds.map((id, i) => ( forceShowSelf={forceShowSelf}
<PeerChip onClick={handleItemClick}
className={styles.peerChip} clickArg={id}
peerId={id} />
isMinimized={ ))}
shouldMinimize && i + (lockedSelectedIds?.length || 0) < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT {unlockedSelectedIds.map((id, i) => (
} <PeerChip
canClose className={styles.peerChip}
onClick={handleItemClick} peerId={id}
clickArg={id} isMinimized={
/> shouldMinimize
))} && i + (lockedSelectedIds?.length || 0) < selectedIds.length - ALWAYS_FULL_ITEMS_COUNT
<InputText }
id={searchInputId} canClose
ref={inputRef} onClick={handleItemClick}
value={filterValue} clickArg={id}
onChange={handleFilterChange} />
placeholder={filterPlaceholder || oldLang('SelectChat')} ))}
/> {!withIslands && renderSearchInput()}
</div> </SearchWrapper>
)}
{withIslands && (
<Island>{renderSearchInput()}</Island>
)}
</>
)} )}
{hasContent ? ( {hasContent ? (
<InfiniteScroll renderListWrapper(
className={buildClassName(styles.pickerList, withDefaultPadding && styles.padded, 'custom-scroll')} <InfiniteScroll
items={viewportIds} className={buildClassName(styles.pickerList, withDefaultPadding && styles.padded, 'custom-scroll')}
itemSelector={`.${ITEM_CLASS_NAME}`} items={viewportIds}
beforeChildren={beforeChildren} itemSelector={`.${ITEM_CLASS_NAME}`}
onLoadMore={getMore} beforeChildren={beforeChildren}
noScrollRestore={noScrollRestore} onLoadMore={getMore}
> noScrollRestore={noScrollRestore}
{renderItems()} >
</InfiniteScroll> {renderItems()}
</InfiniteScroll>,
)
) : !isLoading && viewportIds && !viewportIds.length ? ( ) : !isLoading && viewportIds && !viewportIds.length ? (
<p className={styles.noResults}>{notFoundText || 'Sorry, nothing found.'}</p> <p className={styles.noResults}>{notFoundText || 'Sorry, nothing found.'}</p>
) : ( ) : (

View File

@ -32,6 +32,16 @@
} }
} }
.islandHeader {
overflow-y: auto;
display: flex;
flex-flow: row wrap;
flex-shrink: 0;
max-height: 16.5rem;
padding-bottom: 0;
}
.pickerCategoryTitle { .pickerCategoryTitle {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
padding-inline: 0.5rem; padding-inline: 0.5rem;
@ -77,6 +87,16 @@
} }
} }
.islandList {
overflow: hidden;
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: 0;
padding: 0;
}
.noResults { .noResults {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -17,12 +17,28 @@
margin-top: 1rem; margin-top: 1rem;
} }
.left, .bottom { .left {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: 1.4; line-height: 1.4;
} }
.bottom {
overflow: hidden;
display: flex;
flex-direction: column;
max-height: 25rem;
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: max-height 0.25s ease-in-out, opacity 0.25s ease-in-out;
}
.collapsed {
max-height: 0;
opacity: 0;
}
.status { .status {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--color-error); color: var(--color-error);
@ -108,31 +124,3 @@
animation-name: none; animation-name: none;
} }
} }
@include mixins.on-active-vt('profileBusinessHoursExpand') {
&::view-transition-old(.expandArrow) {
animation-name: vt-expand-icon-spin;
}
&::view-transition-new(.expandArrow) {
display: none;
}
&::view-transition-old(.businessHours) {
display: none;
}
}
@include mixins.on-active-vt('profileBusinessHoursCollapse') {
&::view-transition-old(.expandArrow) {
animation-name: vt-collapse-icon-spin;
}
&::view-transition-new(.expandArrow) {
display: none;
}
&::view-transition-new(.businessHours) {
display: none;
}
}

View File

@ -7,8 +7,6 @@ import type { ApiBusinessWorkHours } from '../../../api/types';
import { selectTimezones } from '../../../global/selectors'; import { selectTimezones } from '../../../global/selectors';
import { import {
VTT_PROFILE_BUSINESS_HOURS, VTT_PROFILE_BUSINESS_HOURS,
VTT_PROFILE_BUSINESS_HOURS_COLLAPSE,
VTT_PROFILE_BUSINESS_HOURS_EXPAND,
} from '../../../util/animations/viewTransitionTypes'; } from '../../../util/animations/viewTransitionTypes';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -105,13 +103,9 @@ const BusinessHours = ({
const handleClick = useLastCallback(() => { const handleClick = useLastCallback(() => {
if (isExpanded) { if (isExpanded) {
startViewTransition(VTT_PROFILE_BUSINESS_HOURS_COLLAPSE, () => { collapse();
collapse();
});
} else { } else {
startViewTransition(VTT_PROFILE_BUSINESS_HOURS_EXPAND, () => { expand();
expand();
});
} }
}); });
@ -151,36 +145,34 @@ const BusinessHours = ({
</div> </div>
<Icon className={styles.arrow} style={createVtnStyle('expandArrow', true)} name={isExpanded ? 'up' : 'down'} /> <Icon className={styles.arrow} style={createVtnStyle('expandArrow', true)} name={isExpanded ? 'up' : 'down'} />
</div> </div>
{isExpanded && ( <div className={buildClassName(styles.bottom, !isExpanded && styles.collapsed)} aria-hidden={!isExpanded}>
<div className={styles.bottom}> {Boolean(timezoneMinuteDifference) && (
{Boolean(timezoneMinuteDifference) && ( <div
<div className={styles.offsetTrigger}
className={styles.offsetTrigger} style={createVtnStyle('offsetTrigger')}
style={createVtnStyle('offsetTrigger')} role="button"
role="button" tabIndex={isExpanded ? 0 : -1}
tabIndex={0} onMouseDown={!IS_TOUCH_ENV ? handleTriggerOffset : undefined}
onMouseDown={!IS_TOUCH_ENV ? handleTriggerOffset : undefined} onClick={IS_TOUCH_ENV ? handleTriggerOffset : undefined}
onClick={IS_TOUCH_ENV ? handleTriggerOffset : undefined} >
> {oldLang(isMyTime ? 'BusinessHoursProfileSwitchMy' : 'BusinessHoursProfileSwitchLocal')}
{oldLang(isMyTime ? 'BusinessHoursProfileSwitchMy' : 'BusinessHoursProfileSwitchLocal')} </div>
</div> )}
)} <dl className={styles.timetable}>
<dl className={styles.timetable}> {DAYS.map((day) => (
{DAYS.map((day) => ( <>
<> <dt className={buildClassName(styles.weekday, day === currentDay && styles.currentDay)}>
<dt className={buildClassName(styles.weekday, day === currentDay && styles.currentDay)}> {formatWeekday(oldLang, day === 6 ? 0 : day + 1)}
{formatWeekday(oldLang, day === 6 ? 0 : day + 1)} </dt>
</dt> <dd className={styles.schedule}>
<dd className={styles.schedule}> {workHours[day].map((segment) => (
{workHours[day].map((segment) => ( <div>{segment}</div>
<div>{segment}</div> ))}
))} </dd>
</dd> </>
</> ))}
))} </dl>
</dl> </div>
</div>
)}
</ListItem> </ListItem>
); );
}; };

View File

@ -76,7 +76,6 @@ type OwnProps = {
isOwnProfile?: boolean; isOwnProfile?: boolean;
isSavedDialog?: boolean; isSavedDialog?: boolean;
isInSettings?: boolean; isInSettings?: boolean;
withIslands?: boolean;
className?: string; className?: string;
style?: string; style?: string;
}; };
@ -132,7 +131,6 @@ const ChatExtra = ({
className, className,
style, style,
isInSettings, isInSettings,
withIslands,
canViewSubscribers, canViewSubscribers,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { const {
@ -392,17 +390,15 @@ const ChatExtra = ({
); );
} }
const Wrapper = withIslands ? Island : 'div';
return ( return (
<div className={buildClassName('ChatExtra', className)} style={style || createVtnStyle('chatExtra')}> <div className={buildClassName('ChatExtra', className)} style={style || createVtnStyle('chatExtra')}>
{user && userFullInfo?.isUnofficialSecurityRisk && ( {user && userFullInfo?.isUnofficialSecurityRisk && (
<Wrapper className={withIslands ? styles.securityRiskIsland : undefined}> <Island className={styles.securityRiskIsland}>
<div className={styles.unofficialSecurityRisk}> <div className={styles.unofficialSecurityRisk}>
<Icon className={buildClassName(styles.riskIcon, 'in-text-icon')} name="info-filled" /> <Icon className={buildClassName(styles.riskIcon, 'in-text-icon')} name="info-filled" />
{lang('UnofficialSecurityRisk', { peer: getPeerTitle(lang, user) })} {lang('UnofficialSecurityRisk', { peer: getPeerTitle(lang, user) })}
</div> </div>
</Wrapper> </Island>
)} )}
{personalChannel && ( {personalChannel && (
<div className={styles.personalChannel} style={createVtnStyle('personalChannel')}> <div className={styles.personalChannel} style={createVtnStyle('personalChannel')}>
@ -410,7 +406,7 @@ const ChatExtra = ({
<span className={styles.personalChannelSubscribers}> <span className={styles.personalChannelSubscribers}>
{oldLang('Subscribers', personalChannel.membersCount, 'i')} {oldLang('Subscribers', personalChannel.membersCount, 'i')}
</span> </span>
<Wrapper className={styles.personalChannelItem}> <Island className={styles.personalChannelItem}>
<Chat <Chat
chatId={personalChannel.id} chatId={personalChannel.id}
orderDiff={0} orderDiff={0}
@ -419,10 +415,10 @@ const ChatExtra = ({
isPreview isPreview
previewMessageId={personalChannelMessageId} previewMessageId={personalChannelMessageId}
/> />
</Wrapper> </Island>
</div> </div>
)} )}
<Wrapper> <Island>
{Boolean(formattedNumber?.length) && ( {Boolean(formattedNumber?.length) && (
<ListItem <ListItem
icon="phone" icon="phone"
@ -647,7 +643,7 @@ const ChatExtra = ({
{botVerification.description} {botVerification.description}
</div> </div>
)} )}
</Wrapper> </Island>
</div> </div>
); );
}; };

View File

@ -1,7 +1,7 @@
@use "../../../styles/mixins"; @use "../../../styles/mixins";
.root { .root {
--profile-info-bg: var(--color-background); --profile-info-bg: var(--color-background-secondary);
--rating-outline-color: #000000; --rating-outline-color: #000000;
--rating-text-color: #000000; --rating-text-color: #000000;

View File

@ -57,3 +57,8 @@
margin-top: 1rem; margin-top: 1rem;
} }
} }
.outside {
padding-bottom: 0.5rem;
padding-inline: 0.5rem;
}

View File

@ -43,6 +43,24 @@ const IslandTitle = ({ className, children, ...otherProps }: OwnProps) => {
); );
}; };
const IslandOutside = ({
className,
children,
...otherProps
}: OwnProps) => {
return (
<div
className={buildClassName(
styles.outside,
className,
)}
{...otherProps}
>
{children}
</div>
);
};
const IslandText = ({ className, children, ...otherProps }: OwnProps) => { const IslandText = ({ className, children, ...otherProps }: OwnProps) => {
return ( return (
<div <div
@ -56,6 +74,7 @@ const IslandText = ({ className, children, ...otherProps }: OwnProps) => {
export default Island; export default Island;
export { export {
IslandOutside,
IslandDescription, IslandDescription,
IslandTitle, IslandTitle,
IslandText, IslandText,

View File

@ -12,6 +12,12 @@
background-color: var(--color-background); background-color: var(--color-background);
transition: background-color 150ms;
&.secondary {
background-color: var(--color-background-secondary);
}
h3 { h3 {
user-select: none; user-select: none;

View File

@ -11,7 +11,8 @@ import type { ReducerAction } from '../../hooks/useReducer';
import { type AnimationLevel, LeftColumnContent, SettingsScreens } from '../../types'; import { type AnimationLevel, LeftColumnContent, SettingsScreens } from '../../types';
import { import {
selectCurrentChat, selectIsCurrentUserFrozen, selectIsForumPanelOpen, selectTabState, selectCurrentChat, selectIsCurrentUserFrozen, selectIsForumPanelOpen,
selectPeerHasProfileBackground, selectTabState,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState'; import { selectSharedSettings } from '../../global/selectors/sharedState';
import { import {
@ -61,6 +62,7 @@ type StateProps = {
archiveSettings: GlobalState['archiveSettings']; archiveSettings: GlobalState['archiveSettings'];
isArchivedStoryRibbonShown?: boolean; isArchivedStoryRibbonShown?: boolean;
isAccountFrozen?: boolean; isAccountFrozen?: boolean;
hasProfileBackground?: boolean;
}; };
enum ContentType { enum ContentType {
@ -98,6 +100,7 @@ function LeftColumn({
archiveSettings, archiveSettings,
isArchivedStoryRibbonShown, isArchivedStoryRibbonShown,
isAccountFrozen, isAccountFrozen,
hasProfileBackground,
isFoldersSidebarShown, isFoldersSidebarShown,
}: OwnProps & StateProps) { }: OwnProps & StateProps) {
const { const {
@ -514,6 +517,7 @@ function LeftColumn({
foldersDispatch={foldersDispatch} foldersDispatch={foldersDispatch}
animationLevel={animationLevel} animationLevel={animationLevel}
shouldSkipTransition={shouldSkipHistoryAnimations} shouldSkipTransition={shouldSkipHistoryAnimations}
hasProfileBackground={hasProfileBackground}
onReset={handleReset} onReset={handleReset}
/> />
); );
@ -627,6 +631,8 @@ export default memo(withGlobal<OwnProps>(
archiveSettings, archiveSettings,
isArchivedStoryRibbonShown: isArchivedRibbonShown, isArchivedStoryRibbonShown: isArchivedRibbonShown,
isAccountFrozen, isAccountFrozen,
hasProfileBackground: currentUserId
? selectPeerHasProfileBackground(global, currentUserId) : undefined,
contentKey: leftColumn.contentKey, contentKey: leftColumn.contentKey,
settingsScreen: leftColumn.settingsScreen, settingsScreen: leftColumn.settingsScreen,
}; };

View File

@ -7,6 +7,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import StarIcon from '../../common/icons/StarIcon'; import StarIcon from '../../common/icons/StarIcon';
import Island, { IslandDescription } from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
type OwnProps = { type OwnProps = {
@ -19,20 +20,19 @@ function PremiumStatusItem({ premiumSection }: OwnProps) {
const handleOpenPremiumModal = useLastCallback(() => openPremiumModal({ initialSection: premiumSection })); const handleOpenPremiumModal = useLastCallback(() => openPremiumModal({ initialSection: premiumSection }));
return ( return (
<div className="settings-item"> <>
<ListItem <Island>
leftElement={<StarIcon className="icon ListItem-main-icon" type="premium" size="big" />} <ListItem
onClick={handleOpenPremiumModal} leftElement={<StarIcon className="icon ListItem-main-icon" type="premium" size="big" />}
> onClick={handleOpenPremiumModal}
{lang('PrivacyLastSeenPremium')} >
</ListItem> {lang('PrivacyLastSeenPremium')}
<p </ListItem>
className="settings-item-description-larger premium-info" </Island>
dir={lang.isRtl ? 'rtl' : undefined} <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
>
{lang('lng_messages_privacy_premium_about')} {lang('lng_messages_privacy_premium_about')}
</p> </IslandDescription>
</div> </>
); );
} }

View File

@ -23,6 +23,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import PaidMessagePrice from '../../common/paidMessage/PaidMessagePrice'; import PaidMessagePrice from '../../common/paidMessage/PaidMessagePrice';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
import PremiumStatusItem from './PremiumStatusItem'; import PremiumStatusItem from './PremiumStatusItem';
@ -159,28 +160,27 @@ function PrivacyMessages({
: oldLang('Users', noPaidReactionsForUsersCount, 'i'); : oldLang('Users', noPaidReactionsForUsersCount, 'i');
return ( return (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('RemoveFeeTitle')} {lang('RemoveFeeTitle')}
</h4> </IslandTitle>
<ListItem <Island>
narrow <ListItem
icon="delete-user" narrow
icon="delete-user"
onClick={() => { onClick={() => {
openSettingsScreen({ screen: SettingsScreens.PrivacyNoPaidMessages }); openSettingsScreen({ screen: SettingsScreens.PrivacyNoPaidMessages });
}} }}
> >
<div className="multiline-item full-size"> <div className="multiline-item full-size">
<span className="title">{lang('ExceptionTitlePrivacyChargeForMessages')}</span> <span className="title">{lang('ExceptionTitlePrivacyChargeForMessages')}</span>
<span className="subtitle"> <span className="subtitle">
{ {itemSubtitle}
itemSubtitle </span>
} </div>
</span> </ListItem>
</div> </Island>
</ListItem> </>
</div>
); );
} }
@ -195,34 +195,34 @@ function PrivacyMessages({
}, [shouldChargeForMessages, lang]); }, [shouldChargeForMessages, lang]);
return ( return (
<> <div className="settings-content custom-scroll">
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> {oldLang('PrivacyMessagesTitle')}
{oldLang('PrivacyMessagesTitle')} </IslandTitle>
</h4> <Island>
<RadioGroup <RadioGroup
name="privacy-messages" name="privacy-messages"
options={options} options={options}
onChange={handleChange} onChange={handleChange}
selected={selectedValue} selected={selectedValue}
/> />
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}> </Island>
{privacyDescription} <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
</p> {privacyDescription}
</div> </IslandDescription>
{selectedValue === 'charge_for_messages' && ( {selectedValue === 'charge_for_messages' && (
<div className="settings-item fluid-container"> <Island>
<PaidMessagePrice <PaidMessagePrice
canChangeChargeForMessages={canChangeChargeForMessages} canChangeChargeForMessages={canChangeChargeForMessages}
chargeForMessages={chargeForMessages} chargeForMessages={chargeForMessages}
onChange={handleChargeForMessagesChange} onChange={handleChargeForMessagesChange}
/> />
</div> </Island>
)} )}
{canChangeChargeForMessages && selectedValue === 'charge_for_messages' && renderSectionNoPaidMessagesForUsers()} {canChangeChargeForMessages && selectedValue === 'charge_for_messages' && renderSectionNoPaidMessagesForUsers()}
{!isCurrentUserPremium && selectedValue !== 'charge_for_messages' {!isCurrentUserPremium && selectedValue !== 'charge_for_messages'
&& <PremiumStatusItem premiumSection="message_privacy" />} && <PremiumStatusItem premiumSection="message_privacy" />}
</> </div>
); );
} }

View File

@ -3,6 +3,14 @@
#Settings { #Settings {
height: 100%; height: 100%;
.chat-list {
padding-bottom: 0;
@media (max-width: 600px) {
padding-block: 0;
}
}
.with-notch::before { .with-notch::before {
top: var(--header-height); top: var(--header-height);
} }
@ -15,11 +23,11 @@
.left-header { .left-header {
padding-right: 0.8125rem; padding-right: 0.8125rem;
}
.self-profile .ProfileInfo { &:has(~ .scrolled),
margin: -0.5rem 0 0.75rem -0.5rem; &:has(~ .settings-fab-wrapper .scrolled) {
margin-inline-end: calc(min(var(--scrollbar-width) - 0.5rem, 0px)); background-color: var(--color-background);
}
} }
} }
@ -43,7 +51,14 @@
.settings-content { .settings-content {
overflow-y: scroll; overflow-y: scroll;
height: calc(100% - var(--header-height)); height: calc(100% - var(--header-height));
padding: 1rem;
padding-top: 0;
background-color: var(--color-background-secondary);
@include mixins.adapt-padding-to-scrollbar(1rem);
&.password-form .input-group.error label::first-letter { &.password-form .input-group.error label::first-letter {
text-transform: uppercase; text-transform: uppercase;
@ -82,7 +97,9 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 0 1.5rem; margin-bottom: 1rem;
padding-block: 0;
padding-inline: 1.5rem;
text-align: center; text-align: center;
@ -105,21 +122,6 @@
} }
} }
.settings-main-menu {
padding: 0.5rem;
@include mixins.adapt-padding-to-scrollbar(0.5rem);
@include mixins.side-panel-section;
.ListItem.narrow:not(.multiline) {
margin-bottom: 0;
.ListItem-button {
min-height: 3.5rem;
}
}
}
.settings-range-value { .settings-range-value {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -137,8 +139,7 @@
} }
.settings-unlock-button { .settings-unlock-button {
margin-top: 1rem; margin-top: 0.5rem;
margin-inline: 1rem;
} }
.fluid-container { .fluid-container {
@ -246,39 +247,6 @@
} }
} }
&.blocked-list-item {
margin-bottom: 0.5rem;
.ListItem-button {
align-items: center;
padding: 0.5rem;
text-align: left;
}
.Avatar {
width: 3rem;
height: 3rem;
margin-right: 1rem;
}
.contact-info {
overflow: hidden;
}
.contact-name {
margin-bottom: 0.25rem;
font-size: 1rem;
font-weight: var(--font-weight-medium);
line-height: 1rem;
}
.contact-phone {
font-size: 0.875rem;
line-height: 1rem;
color: var(--color-text-secondary);
}
}
&[dir="rtl"] { &[dir="rtl"] {
.multiline-item .date { .multiline-item .date {
float: left; float: left;
@ -314,18 +282,63 @@
} }
} }
.settings-language-transition {
height: auto;
margin-top: 1rem;
}
.settings-picker { .settings-picker {
padding-block: 0; padding-block: 0;
} }
.settings-picker-islands {
padding: 1rem;
background-color: var(--color-background-secondary);
}
.settings-input { .settings-input {
padding: 0.5rem 1rem 0 1rem; padding: 0.125rem;
}
.settings-input > :last-child {
--input-group-margin: 0;
} }
.settings-group { .settings-group {
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
} }
.ListItem.blocked-list-item {
.ListItem-button {
align-items: center;
padding: 0.5rem;
text-align: left;
}
.Avatar {
width: 3rem;
height: 3rem;
margin-right: 1rem;
}
.contact-info {
overflow: hidden;
}
.contact-name {
margin-bottom: 0.25rem;
font-size: 1rem;
font-weight: var(--font-weight-medium);
line-height: 1rem;
}
.contact-phone {
font-size: 0.875rem;
line-height: 1rem;
color: var(--color-text-secondary);
}
}
.settings-fab-wrapper { .settings-fab-wrapper {
position: relative; position: relative;
height: calc(100% - var(--header-height)); height: calc(100% - var(--header-height));

View File

@ -155,6 +155,7 @@ export type OwnProps = {
foldersDispatch: FolderEditDispatch; foldersDispatch: FolderEditDispatch;
animationLevel: AnimationLevel; animationLevel: AnimationLevel;
shouldSkipTransition?: boolean; shouldSkipTransition?: boolean;
hasProfileBackground?: boolean;
onReset: (forceReturnToChatList?: true | Event) => void; onReset: (forceReturnToChatList?: true | Event) => void;
}; };
@ -166,6 +167,7 @@ const Settings: FC<OwnProps> = ({
onReset, onReset,
animationLevel, animationLevel,
shouldSkipTransition, shouldSkipTransition,
hasProfileBackground,
}) => { }) => {
const { closeShareChatFolderModal, openSettingsScreen } = getActions(); const { closeShareChatFolderModal, openSettingsScreen } = getActions();
@ -176,7 +178,8 @@ const Settings: FC<OwnProps> = ({
useScrollNotch({ useScrollNotch({
containerRef, containerRef,
selector: '.settings-content', selector: '.Transition_slide-active .settings-content,'
+ ' .Transition_slide-active .settings-main-scroll',
}, [currentScreen]); }, [currentScreen]);
const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => { const handleReset = useLastCallback((forceReturnToChatList?: true | Event) => {
@ -513,6 +516,7 @@ const Settings: FC<OwnProps> = ({
currentScreen={currentScreen} currentScreen={currentScreen}
onReset={handleReset} onReset={handleReset}
editedFolderId={foldersState.folderId} editedFolderId={foldersState.folderId}
hasProfileBackground={hasProfileBackground}
/> />
{renderCurrentSectionContent(isScreenActive, activeKey)} {renderCurrentSectionContent(isScreenActive, activeKey)}
</> </>

View File

@ -8,6 +8,7 @@ import { selectIsCurrentUserPremium } from '../../../global/selectors';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import Switcher from '../../ui/Switcher'; import Switcher from '../../ui/Switcher';
@ -89,54 +90,56 @@ const SettingsAcceptedGift = ({
}); });
return ( return (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyAcceptedGiftTitle')} {lang('PrivacyAcceptedGiftTitle')}
</h4> </IslandTitle>
<ListItem onClick={handleLimitedEditionChange}> <Island>
<span>{lang('PrivacyGiftLimitedEdition')}</span> <ListItem onClick={handleLimitedEditionChange}>
<Switcher <span>{lang('PrivacyGiftLimitedEdition')}</span>
id="limited_edition" <Switcher
label={disallowedGifts?.shouldDisallowLimitedStarGifts ? lang('PrivacyDisableLimitedEditionStarGifts') id="limited_edition"
: lang('PrivacyEnableLimitedEditionStarGifts')} label={disallowedGifts?.shouldDisallowLimitedStarGifts ? lang('PrivacyDisableLimitedEditionStarGifts')
disabled={!isCurrentUserPremium} : lang('PrivacyEnableLimitedEditionStarGifts')}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowLimitedStarGifts} disabled={!isCurrentUserPremium}
/> checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowLimitedStarGifts}
</ListItem> />
<ListItem onClick={handleUnlimitedEditionChange}> </ListItem>
<span>{lang('PrivacyGiftUnlimited')}</span> <ListItem onClick={handleUnlimitedEditionChange}>
<Switcher <span>{lang('PrivacyGiftUnlimited')}</span>
id="unlimited" <Switcher
label={disallowedGifts?.shouldDisallowUnlimitedStarGifts ? lang('PrivacyDisableUnlimitedStarGifts') id="unlimited"
: lang('PrivacyEnableUnlimitedStarGifts')} label={disallowedGifts?.shouldDisallowUnlimitedStarGifts ? lang('PrivacyDisableUnlimitedStarGifts')
disabled={!isCurrentUserPremium} : lang('PrivacyEnableUnlimitedStarGifts')}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowUnlimitedStarGifts} disabled={!isCurrentUserPremium}
/> checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowUnlimitedStarGifts}
</ListItem> />
<ListItem onClick={handleUniqueChange}> </ListItem>
<span>{lang('PrivacyGiftUnique')}</span> <ListItem onClick={handleUniqueChange}>
<Switcher <span>{lang('PrivacyGiftUnique')}</span>
id="unique" <Switcher
label={disallowedGifts?.shouldDisallowUniqueStarGifts ? lang('PrivacyDisableUniqueStarGifts') id="unique"
: lang('PrivacyEnableUniqueStarGifts')} label={disallowedGifts?.shouldDisallowUniqueStarGifts ? lang('PrivacyDisableUniqueStarGifts')
disabled={!isCurrentUserPremium} : lang('PrivacyEnableUniqueStarGifts')}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowUniqueStarGifts} disabled={!isCurrentUserPremium}
/> checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowUniqueStarGifts}
</ListItem> />
<ListItem onClick={handlePremiumSubscriptionChange}> </ListItem>
<span>{lang('PrivacyGiftPremiumSubscription')}</span> <ListItem onClick={handlePremiumSubscriptionChange}>
<Switcher <span>{lang('PrivacyGiftPremiumSubscription')}</span>
id="premium_subscription" <Switcher
label={disallowedGifts?.shouldDisallowPremiumGifts ? lang('PrivacyDisablePremiumGifts') id="premium_subscription"
: lang('PrivacyEnablePremiumGifts')} label={disallowedGifts?.shouldDisallowPremiumGifts ? lang('PrivacyDisablePremiumGifts')
disabled={!isCurrentUserPremium} : lang('PrivacyEnablePremiumGifts')}
checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowPremiumGifts} disabled={!isCurrentUserPremium}
/> checked={!isCurrentUserPremium ? true : !disallowedGifts?.shouldDisallowPremiumGifts}
</ListItem> />
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}> </ListItem>
</Island>
<IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyAcceptedGiftInfo')} {lang('PrivacyAcceptedGiftInfo')}
</p> </IslandDescription>
</div> </>
); );
}; };

View File

@ -15,6 +15,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island, { IslandTitle } from '../../gili/layout/Island';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
@ -141,76 +142,79 @@ const SettingsActiveSessions: FC<OwnProps & StateProps> = ({
function renderCurrentSession(session: ApiSession) { function renderCurrentSession(session: ApiSession) {
return ( return (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('AuthSessionsCurrentSession')} {lang('AuthSessionsCurrentSession')}
</h4> </IslandTitle>
<Island>
<ListItem narrow inactive icon={`device-${getSessionIcon(session)}`} iconClassName="icon-device"> <ListItem narrow inactive icon={`device-${getSessionIcon(session)}`} iconClassName="icon-device">
<div className="multiline-item full-size" dir="auto"> <div className="multiline-item full-size" dir="auto">
<span className="title" dir="auto">{session.deviceModel}</span> <span className="title" dir="auto">{session.deviceModel}</span>
<span className="subtitle black tight"> <span className="subtitle black tight">
{session.appName} {session.appName}
{' '} {' '}
{session.appVersion} {session.appVersion}
, ,
{' '} {' '}
{session.platform} {session.platform}
{' '} {' '}
{session.systemVersion} {session.systemVersion}
</span> </span>
<span className="subtitle"> <span className="subtitle">
{session.ip} {session.ip}
{' '} {' '}
- -
{' '} {' '}
{getLocation(session)} {getLocation(session)}
</span> </span>
</div> </div>
</ListItem>
{hasOtherSessions && (
<ListItem
className="destructive mb-0 no-icon"
icon="stop"
ripple
narrow
onClick={openConfirmTerminateAllDialog}
>
{lang('TerminateAllSessions')}
</ListItem> </ListItem>
)}
</div> {hasOtherSessions && (
<ListItem
className="destructive mb-0 no-icon"
icon="stop"
ripple
narrow
onClick={openConfirmTerminateAllDialog}
>
{lang('TerminateAllSessions')}
</ListItem>
)}
</Island>
</>
); );
} }
function renderOtherSessions(sessionHashes: string[]) { function renderOtherSessions(sessionHashes: string[]) {
return ( return (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('OtherSessions')} {lang('OtherSessions')}
</h4> </IslandTitle>
<Island>
{sessionHashes.map(renderSession)} {sessionHashes.map(renderSession)}
</div> </Island>
</>
); );
} }
function renderAutoTerminate() { function renderAutoTerminate() {
return ( return (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('TerminateOldSessionHeader')} {lang('TerminateOldSessionHeader')}
</h4> </IslandTitle>
<Island>
<p className="settings-item-description-larger">{lang('IfInactiveFor')}</p> <p className="settings-item-description-larger">{lang('IfInactiveFor')}</p>
<RadioGroup <RadioGroup
name="session_ttl" name="session_ttl"
options={AUTO_TERMINATE_OPTIONS} options={AUTO_TERMINATE_OPTIONS}
selected={autoTerminateValue} selected={autoTerminateValue}
onChange={handleChangeSessionTtl} onChange={handleChangeSessionTtl}
/> />
</div> </Island>
</>
); );
} }

View File

@ -15,6 +15,7 @@ import useOldLang from '../../../hooks/useOldLang';
import Avatar from '../../common/Avatar'; import Avatar from '../../common/Avatar';
import FullNameTitle from '../../common/FullNameTitle'; import FullNameTitle from '../../common/FullNameTitle';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import SettingsActiveWebsite from './SettingsActiveWebsite'; import SettingsActiveWebsite from './SettingsActiveWebsite';
@ -80,13 +81,14 @@ const SettingsActiveWebsites: FC<OwnProps & StateProps> = ({
function renderSessions(sessionHashes: string[]) { function renderSessions(sessionHashes: string[]) {
return ( return (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('WebSessionsTitle')} {lang('WebSessionsTitle')}
</h4> </IslandTitle>
<Island>
{sessionHashes.map(renderSession)} {sessionHashes.map(renderSession)}
</div> </Island>
</>
); );
} }
@ -135,7 +137,7 @@ const SettingsActiveWebsites: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <Island>
<ListItem <ListItem
className="destructive mb-0 no-icon" className="destructive mb-0 no-icon"
icon="stop" icon="stop"
@ -145,10 +147,10 @@ const SettingsActiveWebsites: FC<OwnProps & StateProps> = ({
> >
{lang('AuthSessions.LogOutApplications')} {lang('AuthSessions.LogOutApplications')}
</ListItem> </ListItem>
<p className={buildClassName('settings-item-description', styles.clearHelp)}> </Island>
{lang('ClearOtherWebSessionsHelp')} <IslandDescription className={styles.clearHelp}>
</p> {lang('ClearOtherWebSessionsHelp')}
</div> </IslandDescription>
{renderSessions(orderedHashes)} {renderSessions(orderedHashes)}
<ConfirmDialog <ConfirmDialog
isOpen={isConfirmTerminateAllDialogOpen} isOpen={isConfirmTerminateAllDialogOpen}

View File

@ -16,6 +16,7 @@ import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import StickerSetCard from '../../common/StickerSetCard'; import StickerSetCard from '../../common/StickerSetCard';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
type OwnProps = { type OwnProps = {
@ -67,27 +68,31 @@ const SettingsCustomEmoji: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
{customEmojiSets && ( {customEmojiSets && (
<div className="settings-item"> <>
<Checkbox <Island>
label={lang('SuggestAnimatedEmoji')} <Checkbox
checked={shouldSuggestCustomEmoji} label={lang('SuggestAnimatedEmoji')}
onCheck={handleSuggestCustomEmojiChange} checked={shouldSuggestCustomEmoji}
/> onCheck={handleSuggestCustomEmojiChange}
<div className="mt-4" ref={stickerSettingsRef}> />
{customEmojiSets.map((stickerSet: ApiStickerSet) => ( </Island>
<StickerSetCard <Island>
key={stickerSet.id} <div ref={stickerSettingsRef}>
stickerSet={stickerSet} {customEmojiSets.map((stickerSet: ApiStickerSet) => (
observeIntersection={observeIntersectionForCovers} <StickerSetCard
onClick={handleStickerSetClick} key={stickerSet.id}
noPlay={!canPlayAnimatedEmojis} stickerSet={stickerSet}
/> observeIntersection={observeIntersectionForCovers}
))} onClick={handleStickerSetClick}
</div> noPlay={!canPlayAnimatedEmojis}
<p className="settings-item-description mt-3" dir="auto"> />
))}
</div>
</Island>
<IslandDescription dir="auto">
{renderText(lang('EmojiBotInfo'), ['links'])} {renderText(lang('EmojiBotInfo'), ['links'])}
</p> </IslandDescription>
</div> </>
)} )}
</div> </div>
); );

View File

@ -11,6 +11,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import Island, { IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import RangeSlider from '../../ui/RangeSlider'; import RangeSlider from '../../ui/RangeSlider';
@ -84,7 +85,7 @@ const SettingsDataStorage = ({
const value = AUTODOWNLOAD_FILESIZE_MB_LIMITS.indexOf(autoLoadFileMaxSizeMb); const value = AUTODOWNLOAD_FILESIZE_MB_LIMITS.indexOf(autoLoadFileMaxSizeMb);
return ( return (
<div className="pt-5"> <div>
<RangeSlider <RangeSlider
label={lang('AutoDownloadMaxFileSize')} label={lang('AutoDownloadMaxFileSize')}
min={0} min={0}
@ -106,37 +107,33 @@ const SettingsDataStorage = ({
canAutoLoadInChannels: boolean, canAutoLoadInChannels: boolean,
) { ) {
return ( return (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{title}</h4> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{title}</IslandTitle>
<Island>
<Checkbox <Checkbox
label={lang('AutoDownloadSettingsContacts')} label={lang('AutoDownloadSettingsContacts')}
checked={canAutoLoadFromContacts} checked={canAutoLoadFromContacts}
// TODO rewrite to support `useCallback` // TODO rewrite to support `useCallback`
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })}
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}FromContacts`]: isChecked })} />
/> <Checkbox
<Checkbox label={lang('AutoDownloadSettingsPrivateChats')}
label={lang('AutoDownloadSettingsPrivateChats')} checked={canAutoLoadInPrivateChats}
checked={canAutoLoadInPrivateChats} onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })}
/>
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InPrivateChats`]: isChecked })} <Checkbox
/> label={lang('AutoDownloadSettingsGroupChats')}
<Checkbox checked={canAutoLoadInGroups}
label={lang('AutoDownloadSettingsGroupChats')} onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })}
checked={canAutoLoadInGroups} />
<Checkbox
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InGroups`]: isChecked })} label={lang('AutoDownloadSettingsChannels')}
/> checked={canAutoLoadInChannels}
<Checkbox onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })}
label={lang('AutoDownloadSettingsChannels')} />
checked={canAutoLoadInChannels} {key === 'File' && renderContentSizeSlider()}
</Island>
onCheck={(isChecked) => setSettingOption({ [`canAutoLoad${key}InChannels`]: isChecked })} </>
/>
{key === 'File' && renderContentSizeSlider()}
</div>
); );
} }
@ -166,7 +163,7 @@ const SettingsDataStorage = ({
canAutoLoadFileInGroups, canAutoLoadFileInGroups,
canAutoLoadFileInChannels, canAutoLoadFileInChannels,
)} )}
<div className="settings-item"> <Island>
<ListItem <ListItem
onClick={handlePurge} onClick={handlePurge}
icon="delete" icon="delete"
@ -179,7 +176,7 @@ const SettingsDataStorage = ({
{lang('SettingsDataClearMediaCacheDescription')} {lang('SettingsDataClearMediaCacheDescription')}
</span> </span>
</ListItem> </ListItem>
</div> </Island>
</div> </div>
); );
}; };

View File

@ -5,5 +5,6 @@
.item { .item {
overflow: hidden; overflow: hidden;
min-height: 25rem; flex: 1 1 auto;
padding-block: 0;
} }

View File

@ -13,6 +13,8 @@ import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import ItemPicker, { type ItemPickerOption } from '../../common/pickers/ItemPicker'; import ItemPicker, { type ItemPickerOption } from '../../common/pickers/ItemPicker';
import Island from '../../gili/layout/Island';
import InputText from '../../ui/InputText';
import styles from './SettingsDoNotTranslate.module.scss'; import styles from './SettingsDoNotTranslate.module.scss';
@ -79,7 +81,16 @@ const SettingsDoNotTranslate = ({
return ( return (
<div className={buildClassName(styles.root, 'settings-content infinite-scroll')}> <div className={buildClassName(styles.root, 'settings-content infinite-scroll')}>
<div className={buildClassName(styles.item)}> <Island>
<InputText
id="lang-picker-search"
value={searchQuery}
onChange={(e) => setSearchQuery(e.currentTarget.value)}
placeholder={lang('Search')}
noMargin
/>
</Island>
<Island className={styles.item}>
<ItemPicker <ItemPicker
className={styles.picker} className={styles.picker}
items={displayedOptionList} items={displayedOptionList}
@ -87,13 +98,11 @@ const SettingsDoNotTranslate = ({
onSelectedValuesChange={handleChange} onSelectedValuesChange={handleChange}
filterValue={searchQuery} filterValue={searchQuery}
onFilterChange={setSearchQuery} onFilterChange={setSearchQuery}
isSearchable
allowMultiple allowMultiple
withDefaultPadding withDefaultPadding
itemInputType="checkbox" itemInputType="checkbox"
searchInputId="lang-picker-search"
/> />
</div> </Island>
</div> </div>
); );
}; };

View File

@ -26,6 +26,8 @@ import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
import ManageUsernames from '../../common/ManageUsernames'; import ManageUsernames from '../../common/ManageUsernames';
import SafeLink from '../../common/SafeLink'; import SafeLink from '../../common/SafeLink';
import UsernameInput from '../../common/UsernameInput'; import UsernameInput from '../../common/UsernameInput';
import Island, { IslandDescription, IslandOutside, IslandTitle } from '../../gili/layout/Island';
import Surface from '../../gili/layout/Surface';
import AvatarEditable from '../../ui/AvatarEditable'; import AvatarEditable from '../../ui/AvatarEditable';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import InputText from '../../ui/InputText'; import InputText from '../../ui/InputText';
@ -218,28 +220,30 @@ const SettingsEditProfile = ({
const purchaseInfoLink = `${TME_LINK_PREFIX}${PURCHASE_USERNAME}`; const purchaseInfoLink = `${TME_LINK_PREFIX}${PURCHASE_USERNAME}`;
return ( return (
<p className="settings-item-description" dir={oldLang.isRtl ? 'rtl' : undefined}> <IslandDescription dir={oldLang.isRtl ? 'rtl' : undefined}>
{(oldLang('lng_username_purchase_available')) {(oldLang('lng_username_purchase_available'))
.replace('{link}', '%PURCHASE_LINK%') .replace('{link}', '%PURCHASE_LINK%')
.split('%') .split('%')
.map((s) => { .map((s) => {
return (s === 'PURCHASE_LINK' ? <SafeLink url={purchaseInfoLink} text={`@${PURCHASE_USERNAME}`} /> : s); return (s === 'PURCHASE_LINK' ? <SafeLink url={purchaseInfoLink} text={`@${PURCHASE_USERNAME}`} /> : s);
})} })}
</p> </IslandDescription>
); );
} }
return ( return (
<div className="settings-fab-wrapper"> <div className="settings-fab-wrapper">
<div className="settings-content no-border custom-scroll"> <Surface scrollable className="settings-content no-border">
<div className="settings-item"> <IslandOutside className="settings-content-header">
<AvatarEditable
currentAvatarBlobUrl={currentAvatarBlobUrl}
onChange={handlePhotoChange}
title={lang('AriaSettingsEditProfilePhoto')}
disabled={isLoading}
/>
</IslandOutside>
<Island>
<div className="settings-input"> <div className="settings-input">
<AvatarEditable
currentAvatarBlobUrl={currentAvatarBlobUrl}
onChange={handlePhotoChange}
title={lang('AriaSettingsEditProfilePhoto')}
disabled={isLoading}
/>
<InputText <InputText
value={firstName} value={firstName}
onChange={handleFirstNameChange} onChange={handleFirstNameChange}
@ -262,13 +266,12 @@ const SettingsEditProfile = ({
maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined} maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined}
/> />
</div> </div>
</Island>
<IslandDescription dir={oldLang.isRtl ? 'rtl' : undefined}>
{renderText(oldLang('lng_settings_about_bio'), ['br', 'simple_markdown'])}
</IslandDescription>
<p className="settings-item-description" dir={oldLang.isRtl ? 'rtl' : undefined}> <Island>
{renderText(oldLang('lng_settings_about_bio'), ['br', 'simple_markdown'])}
</p>
</div>
<div className="settings-item">
<ListItem <ListItem
icon="gift" icon="gift"
narrow narrow
@ -279,21 +282,20 @@ const SettingsEditProfile = ({
> >
<span className="flex-grow">{lang('SettingsBirthday')}</span> <span className="flex-grow">{lang('SettingsBirthday')}</span>
</ListItem> </ListItem>
<p className="settings-item-description" dir={oldLang.isRtl ? 'rtl' : undefined}> </Island>
{lang('BirthdayPrivacySuggestion', { <IslandDescription dir={oldLang.isRtl ? 'rtl' : undefined}>
link: ( {lang('BirthdayPrivacySuggestion', {
<Link isPrimary onClick={handleBirthdayPrivacyClick}> link: (
{lang('BirthdayPrivacySuggestionLink', <Link isPrimary onClick={handleBirthdayPrivacyClick}>
undefined, { withNodes: true, specialReplacement: NEXT_ARROW_REPLACEMENT })} {lang('BirthdayPrivacySuggestionLink',
</Link> undefined, { withNodes: true, specialReplacement: NEXT_ARROW_REPLACEMENT })}
), </Link>
}, { withNodes: true })} ),
</p> }, { withNodes: true })}
</div> </IslandDescription>
<div className="settings-item">
<h4 className="settings-item-header" dir={oldLang.isRtl ? 'rtl' : undefined}>{oldLang('Username')}</h4>
<IslandTitle dir={oldLang.isRtl ? 'rtl' : undefined}>{oldLang('Username')}</IslandTitle>
<Island>
<div className="settings-input"> <div className="settings-input">
<UsernameInput <UsernameInput
currentUsername={currentUsername} currentUsername={currentUsername}
@ -303,22 +305,21 @@ const SettingsEditProfile = ({
onChange={handleUsernameChange} onChange={handleUsernameChange}
/> />
</div> </div>
</Island>
{editUsernameError === USERNAME_PURCHASE_ERROR && renderPurchaseLink()} {editUsernameError === USERNAME_PURCHASE_ERROR && renderPurchaseLink()}
<p className="settings-item-description" dir={oldLang.isRtl ? 'rtl' : undefined}> <IslandDescription dir={oldLang.isRtl ? 'rtl' : undefined}>
{renderText(oldLang('UsernameHelp'), ['br', 'simple_markdown'])} {renderText(oldLang('UsernameHelp'), ['br', 'simple_markdown'])}
</p> </IslandDescription>
{editableUsername && ( {editableUsername && (
<p className="settings-item-description" dir={oldLang.isRtl ? 'rtl' : undefined}> <IslandDescription dir={oldLang.isRtl ? 'rtl' : undefined}>
{oldLang('lng_username_link')} {oldLang('lng_username_link')}
<br /> <br />
<span className="username-link"> <span className="username-link">
{TME_LINK_PREFIX} {TME_LINK_PREFIX}
{editableUsername} {editableUsername}
</span> </span>
</p> </IslandDescription>
)} )}
</div>
{shouldRenderUsernamesManage && ( {shouldRenderUsernamesManage && (
<ManageUsernames <ManageUsernames
@ -326,7 +327,7 @@ const SettingsEditProfile = ({
onEditUsername={setEditableUsername} onEditUsername={setEditableUsername}
/> />
)} )}
</div> </Surface>
<FloatingActionButton <FloatingActionButton
isShown={isSaveButtonShown} isShown={isSaveButtonShown}

View File

@ -20,6 +20,7 @@ import useMultiaccountInfo from '../../../hooks/useMultiaccountInfo';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview'; import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import Island from '../../gili/layout/Island';
import { animateSnap } from '../../main/visualEffects/SnapEffectContainer'; import { animateSnap } from '../../main/visualEffects/SnapEffectContainer';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -110,15 +111,15 @@ const SettingsExperimental = ({
/> />
<p className="settings-item-description pt-3" dir="auto">{lang('lng_settings_experimental_about')}</p> <p className="settings-item-description pt-3" dir="auto">{lang('lng_settings_experimental_about')}</p>
</div> </div>
<div className="settings-item"> <Island>
<ListItem <ListItem
href={newAccountUrl} href={newAccountUrl}
icon="add-user" icon="add-user"
> >
<div className="title">Login on Test Server</div> <div className="title">Login on Test Server</div>
</ListItem> </ListItem>
</div> </Island>
<div className="settings-item"> <Island>
<ListItem <ListItem
onClick={handleRequestConfetti} onClick={handleRequestConfetti}
icon="animations" icon="animations"
@ -141,45 +142,38 @@ const SettingsExperimental = ({
> >
<div className="title">Vaporize this button</div> <div className="title">Vaporize this button</div>
</ListItem> </ListItem>
</div> </Island>
<div className="settings-item"> <Island>
<Checkbox <Checkbox
label="Allow HTTP Transport" label="Allow HTTP Transport"
checked={Boolean(shouldAllowHttpTransport)} checked={Boolean(shouldAllowHttpTransport)}
onCheck={() => setSharedSettingOption({ shouldAllowHttpTransport: !shouldAllowHttpTransport })} onCheck={() => setSharedSettingOption({ shouldAllowHttpTransport: !shouldAllowHttpTransport })}
/> />
<Checkbox <Checkbox
label="Force HTTP Transport" label="Force HTTP Transport"
disabled={!shouldAllowHttpTransport} disabled={!shouldAllowHttpTransport}
checked={Boolean(shouldForceHttpTransport)} checked={Boolean(shouldForceHttpTransport)}
onCheck={() => setSharedSettingOption({ shouldForceHttpTransport: !shouldForceHttpTransport })} onCheck={() => setSharedSettingOption({ shouldForceHttpTransport: !shouldForceHttpTransport })}
/> />
</div> </Island>
<div className="settings-item"> <Island>
<Checkbox <Checkbox
label={lang('DebugMenuEnableLogs')} label={lang('DebugMenuEnableLogs')}
checked={Boolean(shouldCollectDebugLogs)} checked={Boolean(shouldCollectDebugLogs)}
onCheck={() => setSharedSettingOption({ shouldCollectDebugLogs: !shouldCollectDebugLogs })} onCheck={() => setSharedSettingOption({ shouldCollectDebugLogs: !shouldCollectDebugLogs })}
/> />
<Checkbox <Checkbox
label="Enable exported senders debug" label="Enable exported senders debug"
checked={Boolean(shouldDebugExportedSenders)} checked={Boolean(shouldDebugExportedSenders)}
onCheck={() => setSharedSettingOption({ shouldDebugExportedSenders: !shouldDebugExportedSenders })} onCheck={() => setSharedSettingOption({ shouldDebugExportedSenders: !shouldDebugExportedSenders })}
/> />
<ListItem <ListItem
onClick={handleDownloadLog} onClick={handleDownloadLog}
icon="bug" icon="bug"
> >
<div className="title">Download log</div> <div className="title">Download log</div>
</ListItem> </ListItem>
</div> </Island>
</div> </div>
); );
}; };

View File

@ -19,6 +19,7 @@ import useAppLayout from '../../../hooks/useAppLayout';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import Island, { IslandTitle } from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
import RangeSlider from '../../ui/RangeSlider'; import RangeSlider from '../../ui/RangeSlider';
@ -117,9 +118,8 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('Settings')}</IslandTitle>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('Settings')}</h4> <Island>
<RangeSlider <RangeSlider
label={lang('TextSize')} label={lang('TextSize')}
min={12} min={12}
@ -127,52 +127,47 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
value={messageTextSize} value={messageTextSize}
onChange={handleMessageTextSizeChange} onChange={handleMessageTextSizeChange}
/> />
<ListItem <ListItem
icon="photo" icon="photo"
narrow narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.GeneralChatBackground })} onClick={() => openSettingsScreen({ screen: SettingsScreens.GeneralChatBackground })}
> >
{lang('ChatBackground')} {lang('ChatBackground')}
</ListItem> </ListItem>
</div> </Island>
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('Theme')}</IslandTitle>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <Island>
{lang('Theme')}
</h4>
<RadioGroup <RadioGroup
name="theme" name="theme"
options={appearanceThemeOptions} options={appearanceThemeOptions}
selected={shouldUseSystemTheme ? 'auto' : theme} selected={shouldUseSystemTheme ? 'auto' : theme}
onChange={handleAppearanceThemeChange} onChange={handleAppearanceThemeChange}
/> />
</div> </Island>
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('SettingsTimeFormat')}</IslandTitle>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <Island>
{lang('SettingsTimeFormat')}
</h4>
<RadioGroup <RadioGroup
name="timeformat" name="timeformat"
options={timeFormatOptions} options={timeFormatOptions}
selected={timeFormat} selected={timeFormat}
onChange={handleTimeFormatChange} onChange={handleTimeFormatChange}
/> />
</div> </Island>
{keyboardSendOptions && ( {keyboardSendOptions && (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('SettingsKeyboard')}</h4> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('SettingsKeyboard')}</IslandTitle>
<Island>
<RadioGroup <RadioGroup
name="keyboard-send-settings" name="keyboard-send-settings"
options={keyboardSendOptions} options={keyboardSendOptions}
onChange={handleMessageSendComboChange} onChange={handleMessageSendComboChange}
selected={messageSendKeyCombo} selected={messageSendKeyCombo}
/> />
</div> </Island>
</>
)} )}
</div> </div>
); );

View File

@ -1,13 +1,29 @@
@use "../../../styles/mixins"; @use "../../../styles/mixins";
.SettingsGeneralBackground { .SettingsGeneralBackground {
padding-bottom: 0 !important;
.settings-wallpapers { .settings-wallpapers {
overflow: clip;
display: grid; display: grid;
grid-auto-rows: 1fr; grid-auto-rows: 1fr;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 0.0625rem; gap: 0.0625rem;
@include mixins.side-panel-section; margin: 1rem -1rem 0;
margin-inline: calc(min(var(--scrollbar-width) - 1rem, 0px));
border-top-left-radius: var(--border-radius-island);
border-top-right-radius: var(--border-radius-island);
> :first-child .media-inner,
> :first-child::after {
border-top-left-radius: var(--border-radius-island);
}
> :nth-child(3) .media-inner,
> :nth-child(3)::after {
border-top-right-radius: var(--border-radius-island);
}
} }
.Loading { .Loading {

View File

@ -19,6 +19,7 @@ import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
@ -124,7 +125,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
return ( return (
<div className="SettingsGeneralBackground settings-content custom-scroll"> <div className="SettingsGeneralBackground settings-content custom-scroll">
<div className="settings-item"> <Island>
<ListItem <ListItem
icon="camera-add" icon="camera-add"
className="mb-0" className="mb-0"
@ -151,7 +152,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
checked={Boolean(isBlurred)} checked={Boolean(isBlurred)}
onChange={handleWallPaperBlurChange} onChange={handleWallPaperBlurChange}
/> />
</div> </Island>
{loadedWallpapers ? ( {loadedWallpapers ? (
<div className="settings-wallpapers"> <div className="settings-wallpapers">

View File

@ -1,6 +1,8 @@
@use "../../../styles/mixins"; @use "../../../styles/mixins";
.SettingsGeneralBackgroundColor { .SettingsGeneralBackgroundColor {
padding-bottom: 0 !important;
&:not(.is-dragging) .handle { &:not(.is-dragging) .handle {
transition: transform 300ms ease; transition: transform 300ms ease;
} }
@ -71,12 +73,16 @@
} }
.predefined-colors { .predefined-colors {
overflow: clip;
display: grid; display: grid;
grid-auto-rows: 1fr; grid-auto-rows: 1fr;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 0.0625rem; gap: 0.0625rem;
@include mixins.side-panel-section; margin: 1rem -1rem 0;
margin-inline: calc(min(var(--scrollbar-width) - 1rem, 0px));
border-top-left-radius: var(--border-radius-island);
border-top-right-radius: var(--border-radius-island);
} }
.predefined-color { .predefined-color {
@ -84,6 +90,14 @@
box-shadow: inset 0 0 0 0 var(--color-background); box-shadow: inset 0 0 0 0 var(--color-background);
transition: box-shadow 300ms ease; transition: box-shadow 300ms ease;
&:nth-child(1) {
border-top-left-radius: var(--border-radius-island);
}
&:nth-child(3) {
border-top-right-radius: var(--border-radius-island);
}
&.active { &.active {
border: 0.125rem solid var(--color-primary); border: 0.125rem solid var(--color-primary);
box-shadow: inset 0 0 0 0.3125rem var(--color-background); box-shadow: inset 0 0 0 0.3125rem var(--color-background);

View File

@ -20,6 +20,7 @@ import { pick } from '../../../util/iteratees';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import Island from '../../gili/layout/Island';
import InputText from '../../ui/InputText'; import InputText from '../../ui/InputText';
import './SettingsGeneralBackgroundColor.scss'; import './SettingsGeneralBackgroundColor.scss';
@ -211,7 +212,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
return ( return (
<div ref={containerRef} className={className}> <div ref={containerRef} className={className}>
<div className="settings-item pt-3"> <Island>
<div ref={colorPickerRef} className="color-picker"> <div ref={colorPickerRef} className="color-picker">
<canvas /> <canvas />
<div <div
@ -230,7 +231,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
<InputText value={hexInput} label="HEX" onChange={handleHexChange} /> <InputText value={hexInput} label="HEX" onChange={handleHexChange} />
<InputText value={rgbInput} label="RGB" onChange={handleRgbChange} /> <InputText value={rgbInput} label="RGB" onChange={handleRgbChange} />
</div> </div>
</div> </Island>
<div className="predefined-colors"> <div className="predefined-colors">
{PREDEFINED_COLORS.map((color) => ( {PREDEFINED_COLORS.map((color) => (
<div <div

View File

@ -19,12 +19,14 @@ import MenuItem from '../../ui/MenuItem';
type OwnProps = { type OwnProps = {
currentScreen: SettingsScreens; currentScreen: SettingsScreens;
editedFolderId?: number; editedFolderId?: number;
hasProfileBackground?: boolean;
onReset: () => void; onReset: () => void;
}; };
const SettingsHeader: FC<OwnProps> = ({ const SettingsHeader: FC<OwnProps> = ({
currentScreen, currentScreen,
editedFolderId, editedFolderId,
hasProfileBackground,
onReset, onReset,
}) => { }) => {
const { const {
@ -286,7 +288,9 @@ const SettingsHeader: FC<OwnProps> = ({
} }
return ( return (
<div className="left-header"> <div className={hasProfileBackground && currentScreen === SettingsScreens.Main
? 'left-header' : 'left-header secondary'}
>
<Button <Button
round round
size="smaller" size="smaller"

View File

@ -19,9 +19,11 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import ItemPicker, { type ItemPickerOption } from '../../common/pickers/ItemPicker'; import ItemPicker, { type ItemPickerOption } from '../../common/pickers/ItemPicker';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import Transition from '../../ui/Transition';
type OwnProps = { type OwnProps = {
isActive?: boolean; isActive?: boolean;
@ -131,51 +133,56 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content settings-language custom-scroll"> <div className="settings-content settings-language custom-scroll">
{IS_TRANSLATION_SUPPORTED && ( {IS_TRANSLATION_SUPPORTED && (
<div className="settings-item"> <>
<Checkbox <Island>
label={lang('ShowTranslateButton')} <Checkbox
checked={canTranslate} label={lang('ShowTranslateButton')}
onCheck={handleShouldTranslateChange} checked={canTranslate}
/> onCheck={handleShouldTranslateChange}
<Checkbox />
label={lang('ShowTranslateChatButton')} <Checkbox
checked={canTranslateChatsEnabled} label={lang('ShowTranslateChatButton')}
disabled={!isCurrentUserPremium} checked={canTranslateChatsEnabled}
rightIcon={!isCurrentUserPremium ? 'lock' : undefined} disabled={!isCurrentUserPremium}
onClickLabel={handleShouldTranslateChatsClick} rightIcon={!isCurrentUserPremium ? 'lock' : undefined}
onCheck={handleShouldTranslateChatsChange} onClickLabel={handleShouldTranslateChatsClick}
/> onCheck={handleShouldTranslateChatsChange}
{(canTranslate || canTranslateChatsEnabled) && ( />
<ListItem {(canTranslate || canTranslateChatsEnabled) && (
narrow <ListItem
onClick={handleDoNotSelectOpen} narrow
> onClick={handleDoNotSelectOpen}
{lang('DoNotTranslate')} >
<span className="settings-item__current-value">{doNotTranslateText}</span> {lang('DoNotTranslate')}
</ListItem> <span className="settings-item__current-value">{doNotTranslateText}</span>
)} </ListItem>
<p className="settings-item-description mb-0 mt-1"> )}
</Island>
<IslandDescription>
{lang('lng_translate_settings_about')} {lang('lng_translate_settings_about')}
</p> </IslandDescription>
</div> </>
)} )}
<div className="settings-item settings-item-picker">
<h4 className="settings-item-header"> <Transition activeKey={options ? 1 : 0} name="fade" className="settings-language-transition">
{lang('Localization.InterfaceLanguage')}
</h4>
{options ? ( {options ? (
<ItemPicker <>
items={options} <IslandTitle>{lang('Localization.InterfaceLanguage')}</IslandTitle>
selectedValue={selectedLanguage} <Island>
forceRenderAllItems <ItemPicker
onSelectedValueChange={handleChange} items={options}
itemInputType="radio" selectedValue={selectedLanguage}
className="settings-picker" forceRenderAllItems
/> onSelectedValueChange={handleChange}
itemInputType="radio"
className="settings-picker"
/>
</Island>
</>
) : ( ) : (
<Loading /> <Loading />
)} )}
</div> </Transition>
</div> </div>
); );
}; };

View File

@ -0,0 +1,22 @@
@use "../../../styles/mixins";
.root {
overflow-y: scroll;
height: calc(100% - var(--header-height));
background-color: var(--color-background-secondary);
}
.menuSection,
.selfProfile {
padding: 1rem;
@include mixins.adapt-padding-to-scrollbar(1rem);
}
.selfProfile {
padding-bottom: 0;
:global(.ProfileInfo) {
margin: -1rem 0 0.75rem -1rem;
margin-inline-end: calc(min(var(--scrollbar-width) - 1rem, 0px));
}
}

View File

@ -11,6 +11,7 @@ import {
selectIsGiveawayGiftsPurchaseAvailable, selectIsGiveawayGiftsPurchaseAvailable,
selectIsPremiumPurchaseBlocked, selectIsPremiumPurchaseBlocked,
} from '../../../global/selectors'; } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { convertCurrencyFromBaseUnit } from '../../../util/formatCurrency'; import { convertCurrencyFromBaseUnit } from '../../../util/formatCurrency';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
@ -22,9 +23,12 @@ import Icon from '../../common/icons/Icon';
import StarIcon from '../../common/icons/StarIcon'; import StarIcon from '../../common/icons/StarIcon';
import ChatExtra from '../../common/profile/ChatExtra'; import ChatExtra from '../../common/profile/ChatExtra';
import ProfileInfo from '../../common/profile/ProfileInfo'; import ProfileInfo from '../../common/profile/ProfileInfo';
import Island from '../../gili/layout/Island';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import styles from './SettingsMain.module.scss';
type OwnProps = { type OwnProps = {
isActive?: boolean; isActive?: boolean;
onReset: () => void; onReset: () => void;
@ -80,8 +84,8 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
}); });
return ( return (
<div className="settings-content custom-scroll"> <div className={buildClassName(styles.root, 'settings-main-scroll', 'custom-scroll')}>
<div className="settings-main-menu self-profile"> <div className={styles.selfProfile}>
{currentUserId && ( {currentUserId && (
<ProfileInfo <ProfileInfo
peerId={currentUserId} peerId={currentUserId}
@ -96,153 +100,141 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
/> />
)} )}
</div> </div>
<div className="settings-main-menu"> <div className={styles.menuSection}>
<ListItem <Island>
icon="settings"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.General })}
>
{lang('TelegramGeneralSettingsViewController')}
</ListItem>
<ListItem
icon="animations"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Performance })}
>
{lang('MenuAnimations')}
</ListItem>
<ListItem
icon="unmute"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Notifications })}
>
{lang('Notifications')}
</ListItem>
<ListItem
icon="data"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.DataStorage })}
>
{lang('DataSettings')}
</ListItem>
<ListItem
icon="lock"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Privacy })}
>
{lang('PrivacySettings')}
</ListItem>
<ListItem
icon="folder"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Folders })}
>
{lang('Filters')}
</ListItem>
<ListItem
icon="active-sessions"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.ActiveSessions })}
>
{lang('SessionsTitle')}
{sessionCount > 0 && (<span className="settings-item__current-value">{sessionCount}</span>)}
</ListItem>
<ListItem
icon="language"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Language })}
>
{lang('Language')}
<span className="settings-item__current-value">{lang.languageInfo.nativeName}</span>
</ListItem>
<ListItem
icon="stickers"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Stickers })}
>
{lang('MenuStickers')}
</ListItem>
</div>
<div className="settings-main-menu">
{canBuyPremium && (
<ListItem <ListItem
leftElement={<StarIcon className="icon ListItem-main-icon" type="premium" size="big" />} icon="settings"
narrow narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.General })}
onClick={() => openPremiumModal()}
> >
{lang('TelegramPremium')} {lang('TelegramGeneralSettingsViewController')}
</ListItem> </ListItem>
)}
<ListItem
leftElement={<StarIcon className="icon ListItem-main-icon" type="gold" size="big" />}
narrow
onClick={() => openStarsBalanceModal({})}
>
{lang('MenuStars')}
{Boolean(starsBalance) && (
<span className="settings-item__current-value">
{formatStarsAmount(lang, starsBalance)}
</span>
)}
</ListItem>
<ListItem
leftElement={<Icon className="icon ListItem-main-icon" name="toncoin" />}
narrow
onClick={() => openStarsBalanceModal({ currency: TON_CURRENCY_CODE })}
>
{lang('MenuTon')}
{Boolean(tonBalance) && (
<span className="settings-item__current-value">
{convertCurrencyFromBaseUnit(tonBalance.amount, tonBalance.currency)}
</span>
)}
</ListItem>
{isGiveawayAvailable && (
<ListItem <ListItem
icon="gift" icon="animations"
narrow narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Performance })}
onClick={() => openGiftRecipientPicker()}
> >
{lang('MenuSendGift')} {lang('MenuAnimations')}
</ListItem> </ListItem>
)} <ListItem
</div> icon="unmute"
<div className="settings-main-menu"> narrow
<ListItem onClick={() => openSettingsScreen({ screen: SettingsScreens.Notifications })}
icon="ask-support" >
narrow {lang('Notifications')}
onClick={openSupportDialog} </ListItem>
> <ListItem
{lang('AskAQuestion')} icon="data"
</ListItem> narrow
<ListItem onClick={() => openSettingsScreen({ screen: SettingsScreens.DataStorage })}
icon="help" >
narrow {lang('DataSettings')}
</ListItem>
onClick={() => openUrl({ url: FAQ_URL })} <ListItem
> icon="lock"
{lang('MenuTelegramFaq')} narrow
</ListItem> onClick={() => openSettingsScreen({ screen: SettingsScreens.Privacy })}
<ListItem >
icon="privacy-policy" {lang('PrivacySettings')}
narrow </ListItem>
<ListItem
onClick={() => openUrl({ url: PRIVACY_URL })} icon="folder"
> narrow
{lang('MenuPrivacyPolicy')} onClick={() => openSettingsScreen({ screen: SettingsScreens.Folders })}
</ListItem> >
{lang('Filters')}
</ListItem>
<ListItem
icon="active-sessions"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.ActiveSessions })}
>
{lang('SessionsTitle')}
{sessionCount > 0 && (<span className="settings-item__current-value">{sessionCount}</span>)}
</ListItem>
<ListItem
icon="language"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Language })}
>
{lang('Language')}
<span className="settings-item__current-value">{lang.languageInfo.nativeName}</span>
</ListItem>
<ListItem
icon="stickers"
narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.Stickers })}
>
{lang('MenuStickers')}
</ListItem>
</Island>
<Island>
{canBuyPremium && (
<ListItem
leftElement={<StarIcon className="icon ListItem-main-icon" type="premium" size="big" />}
narrow
onClick={() => openPremiumModal()}
>
{lang('TelegramPremium')}
</ListItem>
)}
<ListItem
leftElement={<StarIcon className="icon ListItem-main-icon" type="gold" size="big" />}
narrow
onClick={() => openStarsBalanceModal({})}
>
{lang('MenuStars')}
{Boolean(starsBalance) && (
<span className="settings-item__current-value">
{formatStarsAmount(lang, starsBalance)}
</span>
)}
</ListItem>
<ListItem
leftElement={<Icon className="icon ListItem-main-icon" name="toncoin" />}
narrow
onClick={() => openStarsBalanceModal({ currency: TON_CURRENCY_CODE })}
>
{lang('MenuTon')}
{Boolean(tonBalance) && (
<span className="settings-item__current-value">
{convertCurrencyFromBaseUnit(tonBalance.amount, tonBalance.currency)}
</span>
)}
</ListItem>
{isGiveawayAvailable && (
<ListItem
icon="gift"
narrow
onClick={() => openGiftRecipientPicker()}
>
{lang('MenuSendGift')}
</ListItem>
)}
</Island>
<Island>
<ListItem
icon="ask-support"
narrow
onClick={openSupportDialog}
>
{lang('AskAQuestion')}
</ListItem>
<ListItem
icon="help"
narrow
onClick={() => openUrl({ url: FAQ_URL })}
>
{lang('MenuTelegramFaq')}
</ListItem>
<ListItem
icon="privacy-policy"
narrow
onClick={() => openUrl({ url: PRIVACY_URL })}
>
{lang('MenuPrivacyPolicy')}
</ListItem>
</Island>
</div> </div>
<ConfirmDialog <ConfirmDialog
isOpen={isSupportDialogOpen} isOpen={isSupportDialogOpen}

View File

@ -13,6 +13,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useRunDebounced from '../../../hooks/useRunDebounced'; import useRunDebounced from '../../../hooks/useRunDebounced';
import Island, { IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import RangeSlider from '../../ui/RangeSlider'; import RangeSlider from '../../ui/RangeSlider';
@ -140,10 +141,10 @@ const SettingsNotifications = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> {lang('NotificationsWeb')}
{lang('NotificationsWeb')} </IslandTitle>
</h4> <Island>
<Checkbox <Checkbox
label={lang('NotificationsWeb')} label={lang('NotificationsWeb')}
subLabel={lang(hasWebNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} subLabel={lang(hasWebNotifications ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
@ -160,22 +161,20 @@ const SettingsNotifications = ({
checked={hasPushNotifications} checked={hasPushNotifications}
onChange={handlePushNotificationsChange} onChange={handlePushNotificationsChange}
/> />
<div className="settings-item-slider"> <RangeSlider
<RangeSlider label={lang('NotificationsSound')}
label={lang('NotificationsSound')} min={0}
min={0} max={10}
max={10} disabled={!areNotificationsSupported}
disabled={!areNotificationsSupported} value={notificationSoundVolume}
value={notificationSoundVolume} onChange={handleVolumeChange}
onChange={handleVolumeChange} />
/> </Island>
</div>
</div>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('AutodownloadPrivateChats')}
</h4>
<IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('AutodownloadPrivateChats')}
</IslandTitle>
<Island>
<Checkbox <Checkbox
label={lang('NotificationsForPrivateChats')} label={lang('NotificationsForPrivateChats')}
subLabel={lang(!areUsersMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} subLabel={lang(!areUsersMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
@ -190,11 +189,10 @@ const SettingsNotifications = ({
checked={Boolean(notifyDefaults?.users?.shouldShowPreviews)} checked={Boolean(notifyDefaults?.users?.shouldShowPreviews)}
onChange={handlePrivateChatsPreviewChange} onChange={handlePrivateChatsPreviewChange}
/> />
</div> </Island>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterGroups')}</h4>
<IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterGroups')}</IslandTitle>
<Island>
<Checkbox <Checkbox
label={lang('NotificationsForGroups')} label={lang('NotificationsForGroups')}
subLabel={lang(!areGroupsMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} subLabel={lang(!areGroupsMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
@ -209,11 +207,10 @@ const SettingsNotifications = ({
checked={Boolean(notifyDefaults?.groups?.shouldShowPreviews)} checked={Boolean(notifyDefaults?.groups?.shouldShowPreviews)}
onChange={handleGroupsPreviewChange} onChange={handleGroupsPreviewChange}
/> />
</div> </Island>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterChannels')}</h4>
<IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterChannels')}</IslandTitle>
<Island>
<Checkbox <Checkbox
label={lang('NotificationsForChannels')} label={lang('NotificationsForChannels')}
subLabel={lang(!areChannelsMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')} subLabel={lang(!areChannelsMuted ? 'UserInfoNotificationsEnabled' : 'UserInfoNotificationsDisabled')}
@ -228,11 +225,10 @@ const SettingsNotifications = ({
checked={Boolean(notifyDefaults?.channels?.shouldShowPreviews)} checked={Boolean(notifyDefaults?.channels?.shouldShowPreviews)}
onChange={handleChannelsPreviewChange} onChange={handleChannelsPreviewChange}
/> />
</div> </Island>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('PhoneOther')}</h4>
<IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('PhoneOther')}</IslandTitle>
<Island>
<Checkbox <Checkbox
label={lang('ContactJoined')} label={lang('ContactJoined')}
checked={hasContactJoinedNotifications} checked={hasContactJoinedNotifications}
@ -243,7 +239,7 @@ const SettingsNotifications = ({
checked={shouldNotifyAboutPinnedMessages} checked={shouldNotifyAboutPinnedMessages}
onChange={handlePinnedMessagesNotificationChange} onChange={handlePinnedMessagesNotificationChange}
/> />
</div> </Island>
</div> </div>
); );
}; };

View File

@ -22,6 +22,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview'; import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import CustomEmoji from '../../common/CustomEmoji'; import CustomEmoji from '../../common/CustomEmoji';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import Link from '../../ui/Link'; import Link from '../../ui/Link';
@ -139,7 +140,7 @@ const SettingsPasskeys = ({
{lang('SettingsPasskeyInfo')} {lang('SettingsPasskeyInfo')}
</p> </p>
</div> </div>
<div className="settings-item"> <Island className="settings-item">
{passkeys?.map(renderPasskey)} {passkeys?.map(renderPasskey)}
{canAddPasskey && ( {canAddPasskey && (
<Button <Button
@ -153,17 +154,17 @@ const SettingsPasskeys = ({
{lang('SettingsPasskeysCreate')} {lang('SettingsPasskeysCreate')}
</Button> </Button>
)} )}
<p className="settings-item-description mt-3" dir="auto"> </Island>
{lang('SettingsPasskeysFooter', { <IslandDescription dir="auto">
link: ( {lang('SettingsPasskeysFooter', {
<Link isPrimary onClick={handleOpenPasskeyModal}> link: (
{lang('SettingsPasskeysFooterLink', undefined, <Link isPrimary onClick={handleOpenPasskeyModal}>
{ withNodes: true, specialReplacement: NEXT_ARROW_REPLACEMENT })} {lang('SettingsPasskeysFooterLink', undefined,
</Link> { withNodes: true, specialReplacement: NEXT_ARROW_REPLACEMENT })}
), </Link>
}, { withNodes: true })} ),
</p> }, { withNodes: true })}
</div> </IslandDescription>
<ConfirmDialog <ConfirmDialog
isOpen={Boolean(deleteModalId)} isOpen={Boolean(deleteModalId)}
title={lang('PasskeyDeleteTitle')} title={lang('PasskeyDeleteTitle')}

View File

@ -6,6 +6,7 @@ import useLang from '../../../hooks/useLang';
import PasswordForm from '../../common/PasswordForm'; import PasswordForm from '../../common/PasswordForm';
import PasswordMonkey from '../../common/PasswordMonkey'; import PasswordMonkey from '../../common/PasswordMonkey';
import Island from '../../gili/layout/Island';
type OwnProps = { type OwnProps = {
error?: string; error?: string;
@ -65,21 +66,23 @@ const SettingsPasswordForm: FC<OwnProps> = ({
<PasswordMonkey isBig isPasswordVisible={shouldShowPassword} /> <PasswordMonkey isBig isPasswordVisible={shouldShowPassword} />
</div> </div>
<div className="settings-item settings-group"> <Island>
<PasswordForm <div className="settings-input">
error={validationError || error} <PasswordForm
hint={hint} error={validationError || error}
placeholder={placeholder || lang('CurrentPasswordPlaceholder')} hint={hint}
shouldDisablePasswordManager={shouldDisablePasswordManager} placeholder={placeholder || lang('CurrentPasswordPlaceholder')}
submitLabel={submitLabel} shouldDisablePasswordManager={shouldDisablePasswordManager}
onClearError={handleClearError} submitLabel={submitLabel}
isLoading={isLoading} onClearError={handleClearError}
isPasswordVisible={shouldShowPassword} isLoading={isLoading}
shouldResetValue={isActive} isPasswordVisible={shouldShowPassword}
onChangePasswordVisibility={setShouldShowPassword} shouldResetValue={isActive}
onSubmit={handleSubmit} onChangePasswordVisibility={setShouldShowPassword}
/> onSubmit={handleSubmit}
</div> />
</div>
</Island>
</div> </div>
); );
}; };

View File

@ -22,6 +22,7 @@ import { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../u
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import RangeSlider from '../../ui/RangeSlider'; import RangeSlider from '../../ui/RangeSlider';
@ -168,24 +169,22 @@ function SettingsPerformance({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> {lang('SettingsPerformanceSliderTitle')}
{lang('SettingsPerformanceSliderTitle')} </IslandTitle>
</h4> <Island>
<p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('SettingsPerformanceSliderSubtitle')}
</p>
<RangeSlider <RangeSlider
options={animationLevelOptions} options={animationLevelOptions}
value={animationLevelState === ANIMATION_LEVEL_CUSTOM ? ANIMATION_LEVEL_MED : animationLevelState} value={animationLevelState === ANIMATION_LEVEL_CUSTOM ? ANIMATION_LEVEL_MED : animationLevelState}
onChange={handleAnimationLevelChange} onChange={handleAnimationLevelChange}
/> />
</div> </Island>
<IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
<div className="settings-item-simple settings-item__with-shifted-dropdown"> {lang('SettingsPerformanceSliderSubtitle')}
<h3 className="settings-item-header" dir="auto">{lang('SettingsPerformanceGranularTitle')}</h3> </IslandDescription>
<IslandTitle dir="auto">{lang('SettingsPerformanceGranularTitle')}</IslandTitle>
<Island>
{PERFORMANCE_OPTIONS.map(([sectionName, options], index) => { {PERFORMANCE_OPTIONS.map(([sectionName, options], index) => {
return ( return (
<div <div
@ -223,7 +222,7 @@ function SettingsPerformance({
</div> </div>
); );
})} })}
</div> </Island>
</div> </div>
); );
} }

View File

@ -19,6 +19,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import StarIcon from '../../common/icons/StarIcon'; import StarIcon from '../../common/icons/StarIcon';
import Island, { IslandTitle } from '../../gili/layout/Island';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -196,11 +197,10 @@ const SettingsPrivacy = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <Island>
<ListItem <ListItem
icon="delete-user" icon="delete-user"
narrow narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyBlockedUsers })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyBlockedUsers })}
> >
{oldLang('BlockedUsers')} {oldLang('BlockedUsers')}
@ -210,7 +210,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
icon="lock" icon="lock"
narrow narrow
onClick={() => openSettingsScreen({ onClick={() => openSettingsScreen({
screen: hasPasscode ? SettingsScreens.PasscodeEnabled : SettingsScreens.PasscodeDisabled, screen: hasPasscode ? SettingsScreens.PasscodeEnabled : SettingsScreens.PasscodeDisabled,
})} })}
@ -226,7 +225,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
icon="admin" icon="admin"
narrow narrow
onClick={() => openSettingsScreen({ onClick={() => openSettingsScreen({
screen: hasPassword ? SettingsScreens.TwoFaEnabled : SettingsScreens.TwoFaDisabled, screen: hasPassword ? SettingsScreens.TwoFaEnabled : SettingsScreens.TwoFaDisabled,
})} })}
@ -257,22 +255,19 @@ const SettingsPrivacy = ({
<ListItem <ListItem
icon="web" icon="web"
narrow narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.ActiveWebsites })} onClick={() => openSettingsScreen({ screen: SettingsScreens.ActiveWebsites })}
> >
{oldLang('PrivacySettings.WebSessions')} {oldLang('PrivacySettings.WebSessions')}
<span className="settings-item__current-value">{webAuthCount}</span> <span className="settings-item__current-value">{webAuthCount}</span>
</ListItem> </ListItem>
)} )}
</div> </Island>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{oldLang('PrivacyTitle')}</h4>
<IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{oldLang('PrivacyTitle')}</IslandTitle>
<Island>
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyPhoneNumber })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyPhoneNumber })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -285,7 +280,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyLastSeen })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyLastSeen })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -298,7 +292,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyProfilePhoto })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyProfilePhoto })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -311,7 +304,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyBio })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyBio })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -324,7 +316,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyBirthday })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyBirthday })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -337,7 +328,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyGifts })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyGifts })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -350,7 +340,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyForwarding })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyForwarding })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -363,7 +352,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyPhoneCall })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyPhoneCall })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -378,7 +366,6 @@ const SettingsPrivacy = ({
allowDisabledClick allowDisabledClick
rightElement={isCurrentUserPremium && <StarIcon size="big" type="premium" />} rightElement={isCurrentUserPremium && <StarIcon size="big" type="premium" />}
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyVoiceMessages })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyVoiceMessages })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -392,7 +379,6 @@ const SettingsPrivacy = ({
narrow narrow
rightElement={isCurrentUserPremium && <StarIcon size="big" type="premium" />} rightElement={isCurrentUserPremium && <StarIcon size="big" type="premium" />}
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyMessages })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyMessages })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -408,7 +394,6 @@ const SettingsPrivacy = ({
<ListItem <ListItem
narrow narrow
className="no-icon" className="no-icon"
onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyGroupChats })} onClick={() => openSettingsScreen({ screen: SettingsScreens.PrivacyGroupChats })}
> >
<div className="multiline-item"> <div className="multiline-item">
@ -418,65 +403,68 @@ const SettingsPrivacy = ({
</span> </span>
</div> </div>
</ListItem> </ListItem>
</div> </Island>
{canChangeSensitive && ( {canChangeSensitive && (
<div className="settings-item fluid-container"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{oldLang('lng_settings_sensitive_title')} {oldLang('lng_settings_sensitive_title')}
</h4> </IslandTitle>
<Checkbox <Island>
label={oldLang('lng_settings_sensitive_disable_filtering')} <Checkbox
subLabel={oldLang('lng_settings_sensitive_about')} label={oldLang('lng_settings_sensitive_disable_filtering')}
checked={Boolean(isSensitiveEnabled)} subLabel={oldLang('lng_settings_sensitive_about')}
disabled={!canChangeSensitive || (!isSensitiveEnabled && needAgeVideoVerification)} checked={Boolean(isSensitiveEnabled)}
onCheck={handleUpdateContentSettings} disabled={!canChangeSensitive || (!isSensitiveEnabled && needAgeVideoVerification)}
/> onCheck={handleUpdateContentSettings}
{!isSensitiveEnabled && needAgeVideoVerification && ( />
<Button {!isSensitiveEnabled && needAgeVideoVerification && (
color="primary" <Button
fluid color="primary"
noForcedUpperCase noForcedUpperCase
className="settings-unlock-button" className="settings-unlock-button"
onClick={handleAgeVerification} onClick={handleAgeVerification}
> >
<span className="settings-unlock-button-title"> <span className="settings-unlock-button-title">
{lang('ButtonAgeVerification')} {lang('ButtonAgeVerification')}
</span> </span>
</Button> </Button>
)} )}
</div> </Island>
</>
)} )}
{canDisplayAutoarchiveSetting && ( {canDisplayAutoarchiveSetting && (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{oldLang('NewChatsFromNonContacts')} {oldLang('NewChatsFromNonContacts')}
</h4> </IslandTitle>
<Checkbox <Island>
label={oldLang('ArchiveAndMute')} <Checkbox
subLabel={oldLang('ArchiveAndMuteInfo')} label={oldLang('ArchiveAndMute')}
checked={Boolean(shouldArchiveAndMuteNewNonContact)} subLabel={oldLang('ArchiveAndMuteInfo')}
onCheck={handleArchiveAndMuteChange} checked={Boolean(shouldArchiveAndMuteNewNonContact)}
/> onCheck={handleArchiveAndMuteChange}
</div> />
</Island>
</>
)} )}
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> {oldLang('lng_settings_window_system')}
{oldLang('lng_settings_window_system')} </IslandTitle>
</h4> <Island>
<Checkbox <Checkbox
label={oldLang('lng_settings_title_chat_name')} label={oldLang('lng_settings_title_chat_name')}
checked={Boolean(canDisplayChatInTitle)} checked={Boolean(canDisplayChatInTitle)}
onCheck={handleChatInTitleChange} onCheck={handleChatInTitleChange}
/> />
</div> </Island>
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> {lang('DeleteMyAccount')}
{lang('DeleteMyAccount')} </IslandTitle>
</h4> <Island>
<ListItem <ListItem
narrow narrow
onClick={handleOpenDeleteAccountModal} onClick={handleOpenDeleteAccountModal}
@ -486,7 +474,7 @@ const SettingsPrivacy = ({
{lang('Months', { count: dayOption }, { pluralValue: 1 })} {lang('Months', { count: dayOption }, { pluralValue: 1 })}
</span> </span>
</ListItem> </ListItem>
</div> </Island>
</div> </div>
); );
}; };

View File

@ -0,0 +1,15 @@
.surface {
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
}
.listIsland {
overflow: hidden;
flex: 0 1 auto;
min-height: 0;
margin-bottom: 1rem;
padding: 0;
}

View File

@ -16,11 +16,15 @@ import useOldLang from '../../../hooks/useOldLang';
import Avatar from '../../common/Avatar'; import Avatar from '../../common/Avatar';
import FullNameTitle from '../../common/FullNameTitle'; import FullNameTitle from '../../common/FullNameTitle';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Surface from '../../gili/layout/Surface';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import BlockUserModal from './BlockUserModal'; import BlockUserModal from './BlockUserModal';
import styles from './SettingsPrivacyBlockedUsers.module.scss';
type OwnProps = { type OwnProps = {
isActive?: boolean; isActive?: boolean;
onReset: () => void; onReset: () => void;
@ -118,25 +122,28 @@ const SettingsPrivacyBlockedUsers: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-fab-wrapper"> <div className="settings-fab-wrapper">
<div className="settings-content infinite-scroll"> <Surface className={styles.surface}>
<div className="settings-item no-border"> <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
<p className="settings-item-description-larger mt-0 mb-2" dir={lang.isRtl ? 'rtl' : undefined}> {lang('BlockedUsersInfo')}
{lang('BlockedUsersInfo')} </IslandDescription>
</p>
</div>
<div className="chat-list custom-scroll"> <Island className={styles.listIsland}>
{blockedIds?.length ? ( <div className="chat-list custom-scroll">
<div className="scroll-container settings-item"> {blockedIds?.length ? (
{blockedIds.map((contactId, i) => renderContact(contactId, i, 0))} <div
</div> className="scroll-container"
) : blockedIds && !blockedIds.length ? ( style={`height: ${blockedIds.length * CHAT_HEIGHT_PX}px`}
<div className="no-results" dir="auto">{lang('NoBlocked')}</div> >
) : ( {blockedIds.map((contactId, i) => renderContact(contactId, i, 0))}
<Loading key="loading" /> </div>
)} ) : blockedIds && !blockedIds.length ? (
</div> <div className="no-results" dir="auto">{lang('NoBlocked')}</div>
</div> ) : (
<Loading key="loading" />
)}
</div>
</Island>
</Surface>
<FloatingActionButton <FloatingActionButton
isShown isShown

View File

@ -10,6 +10,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import StarIcon from '../../common/icons/StarIcon'; import StarIcon from '../../common/icons/StarIcon';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -42,33 +43,32 @@ const SettingsPrivacyLastSeen = ({
return ( return (
<> <>
{canShowHideReadTime && ( {canShowHideReadTime && (
<div className="settings-item"> <>
<Checkbox <Island>
label={lang('HideReadTime')} <Checkbox
checked={shouldHideReadMarks} label={lang('HideReadTime')}
onCheck={handleChangeShouldHideReadMarks} checked={shouldHideReadMarks}
/> onCheck={handleChangeShouldHideReadMarks}
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}> />
</Island>
<IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
{renderText(lang('HideReadTimeInfo'), ['br'])} {renderText(lang('HideReadTimeInfo'), ['br'])}
</p> </IslandDescription>
</div> </>
)} )}
<div className="settings-item"> <Island>
<ListItem <ListItem
leftElement={<StarIcon className="icon ListItem-main-icon" type="premium" size="big" />} leftElement={<StarIcon className="icon ListItem-main-icon" type="premium" size="big" />}
onClick={handleOpenPremiumModal} onClick={handleOpenPremiumModal}
> >
{isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')} {isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')}
</ListItem> </ListItem>
<p </Island>
className="settings-item-description-larger premium-info" <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
dir={lang.isRtl ? 'rtl' : undefined} {isCurrentUserPremium
> ? lang('PrivacyLastSeenPremiumInfoForPremium')
{isCurrentUserPremium : lang('PrivacyLastSeenPremiumInfo')}
? lang('PrivacyLastSeenPremiumInfoForPremium') </IslandDescription>
: lang('PrivacyLastSeenPremiumInfo')}
</p>
</div>
</> </>
); );
}; };

View File

@ -10,6 +10,7 @@ import useFlag from '../../../hooks/useFlag';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Avatar from '../../common/Avatar'; import Avatar from '../../common/Avatar';
import Island, { IslandDescription } from '../../gili/layout/Island';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import SelectAvatar from '../../ui/SelectAvatar'; import SelectAvatar from '../../ui/SelectAvatar';
@ -63,44 +64,46 @@ const SettingsPrivacyPublicProfilePhoto: FC<OwnProps> = ({
}, []); }, []);
return ( return (
<div className="settings-item"> <>
<ListItem <Island>
narrow
icon="camera-add"
onClick={handleOpenFileSelector}
>
<SelectAvatar
onChange={handleSelectFile}
inputRef={inputRef}
/>
{lang(currentUserFallbackPhoto
? 'Privacy.ProfilePhoto.UpdatePublicPhoto'
: 'Privacy.ProfilePhoto.SetPublicPhoto')}
</ListItem>
{currentUserFallbackPhoto && (
<ListItem <ListItem
narrow narrow
leftElement={<Avatar photo={currentUserFallbackPhoto} size="mini" className={styles.fallbackPhoto} />} icon="camera-add"
onClick={openDeleteFallbackPhotoModal} onClick={handleOpenFileSelector}
destructive
> >
{lang(currentUserFallbackPhoto.isVideo <SelectAvatar
? 'Privacy.ProfilePhoto.RemovePublicVideo' onChange={handleSelectFile}
: 'Privacy.ProfilePhoto.RemovePublicPhoto')} inputRef={inputRef}
<ConfirmDialog
isOpen={isDeleteFallbackPhotoModalOpen}
onClose={closeDeleteFallbackPhotoModal}
text={lang('Privacy.ResetPhoto.Confirm')}
confirmLabel={lang('Delete')}
confirmHandler={handleConfirmDelete}
confirmIsDestructive
/> />
{lang(currentUserFallbackPhoto
? 'Privacy.ProfilePhoto.UpdatePublicPhoto'
: 'Privacy.ProfilePhoto.SetPublicPhoto')}
</ListItem> </ListItem>
)} {currentUserFallbackPhoto && (
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}> <ListItem
narrow
leftElement={<Avatar photo={currentUserFallbackPhoto} size="mini" className={styles.fallbackPhoto} />}
onClick={openDeleteFallbackPhotoModal}
destructive
>
{lang(currentUserFallbackPhoto.isVideo
? 'Privacy.ProfilePhoto.RemovePublicVideo'
: 'Privacy.ProfilePhoto.RemovePublicPhoto')}
<ConfirmDialog
isOpen={isDeleteFallbackPhotoModalOpen}
onClose={closeDeleteFallbackPhotoModal}
text={lang('Privacy.ResetPhoto.Confirm')}
confirmLabel={lang('Delete')}
confirmHandler={handleConfirmDelete}
confirmIsDestructive
/>
</ListItem>
)}
</Island>
<IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
{lang('Privacy.ProfilePhoto.PublicPhotoInfo')} {lang('Privacy.ProfilePhoto.PublicPhotoInfo')}
</p> </IslandDescription>
</div> </>
); );
}; };

View File

@ -14,6 +14,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
import Switcher from '../../ui/Switcher'; import Switcher from '../../ui/Switcher';
@ -95,25 +96,27 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
{screen === SettingsScreens.PrivacyGifts && ( {screen === SettingsScreens.PrivacyGifts && (
<div className="settings-item"> <>
<ListItem onClick={handleShowGiftIconInChats}> <Island>
<span>{lang('PrivacyDisplayGiftsButton')}</span> <ListItem onClick={handleShowGiftIconInChats}>
<Switcher <span>{lang('PrivacyDisplayGiftsButton')}</span>
id="gift" <Switcher
disabled={!isCurrentUserPremium} id="gift"
label={shouldDisplayGiftsButton ? lang('HideGiftsButton') : lang('DisplayGiftsButton')} disabled={!isCurrentUserPremium}
checked={shouldDisplayGiftsButton} label={shouldDisplayGiftsButton ? lang('HideGiftsButton') : lang('DisplayGiftsButton')}
/> checked={shouldDisplayGiftsButton}
</ListItem> />
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}> </ListItem>
</Island>
<IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyDisplayGiftIconInChats', { {lang('PrivacyDisplayGiftIconInChats', {
icon: <Icon name="gift" className="gift-icon" />, icon: <Icon name="gift" className="gift-icon" />,
gift: lang('PrivacyDisplayGift'), gift: lang('PrivacyDisplayGift'),
}, { }, {
withNodes: true, withNodes: true,
})} })}
</p> </IslandDescription>
</div> </>
)} )}
<PrivacySubsection <PrivacySubsection
screen={screen} screen={screen}
@ -347,54 +350,54 @@ function PrivacySubsection({
return ( return (
<> <>
<div className="settings-item"> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{headerText}</IslandTitle>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{headerText}</h4> <Island>
<RadioGroup <RadioGroup
name={`visibility-${privacyKey}`} name={`visibility-${privacyKey}`}
options={visibilityOptions} options={visibilityOptions}
onChange={handleVisibilityChange} onChange={handleVisibilityChange}
selected={privacy?.visibility} selected={privacy?.visibility}
/> />
{descriptionText && ( </Island>
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>{descriptionText}</p> {descriptionText && (
)} <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>{descriptionText}</IslandDescription>
</div> )}
{!isPremiumRequired && (primaryExceptionLists.shouldShowAllowed || primaryExceptionLists.shouldShowDenied) && ( {!isPremiumRequired && (primaryExceptionLists.shouldShowAllowed || primaryExceptionLists.shouldShowDenied) && (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{oldLang('PrivacyExceptions')} {oldLang('PrivacyExceptions')}
</h4> </IslandTitle>
{primaryExceptionLists.shouldShowAllowed && ( <Island>
<ListItem {primaryExceptionLists.shouldShowAllowed && (
narrow <ListItem
icon="add-user" narrow
icon="add-user"
onClick={() => { onClick={() => {
openSettingsScreen({ screen: allowedContactsScreen }); openSettingsScreen({ screen: allowedContactsScreen });
}} }}
> >
<div className="multiline-item full-size"> <div className="multiline-item full-size">
<span className="title">{oldLang('AlwaysAllow')}</span> <span className="title">{oldLang('AlwaysAllow')}</span>
<span className="subtitle">{allowedString}</span> <span className="subtitle">{allowedString}</span>
</div> </div>
</ListItem> </ListItem>
)} )}
{primaryExceptionLists.shouldShowDenied && ( {primaryExceptionLists.shouldShowDenied && (
<ListItem <ListItem
narrow narrow
icon="delete-user" icon="delete-user"
onClick={() => {
onClick={() => { openSettingsScreen({ screen: deniedContactsScreen });
openSettingsScreen({ screen: deniedContactsScreen }); }}
}} >
> <div className="multiline-item full-size">
<div className="multiline-item full-size"> <span className="title">{oldLang('NeverAllow')}</span>
<span className="title">{oldLang('NeverAllow')}</span> <span className="subtitle">{blockString}</span>
<span className="subtitle">{blockString}</span> </div>
</div> </ListItem>
</ListItem> )}
)} </Island>
</div> </>
)} )}
{isPremiumRequired && <PremiumStatusItem />} {isPremiumRequired && <PremiumStatusItem />}
</> </>

View File

@ -183,7 +183,7 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
} }
return ( return (
<div className="NewChat-inner step-1"> <div className="Picker settings-picker-islands">
<PeerPicker <PeerPicker
categories={getCustomCategory()} categories={getCustomCategory()}
itemIds={displayedIds || []} itemIds={displayedIds || []}
@ -201,6 +201,7 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
itemInputType="checkbox" itemInputType="checkbox"
withDefaultPadding withDefaultPadding
withStatus withStatus
withIslands
/> />
<FloatingActionButton <FloatingActionButton

View File

@ -7,6 +7,8 @@ import type { ApiAvailableReaction, ApiReaction } from '../../../api/types';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji'; import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji';
import Island from '../../gili/layout/Island';
import Surface from '../../gili/layout/Surface';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
type OwnProps = { type OwnProps = {
@ -52,15 +54,17 @@ const SettingsQuickReaction: FC<OwnProps & StateProps> = ({
}, [setDefaultReaction]); }, [setDefaultReaction]);
return ( return (
<div className="settings-content settings-item custom-scroll settings-quick-reaction"> <Surface scrollable className="settings-content settings-quick-reaction">
<RadioGroup <Island>
name="quick-reaction-settings" <RadioGroup
options={options} name="quick-reaction-settings"
selected={selectedReaction?.type === 'emoji' ? selectedReaction.emoticon : undefined} options={options}
onChange={handleChange} selected={selectedReaction?.type === 'emoji' ? selectedReaction.emoticon : undefined}
withIcon onChange={handleChange}
/> withIcon
</div> />
</Island>
</Surface>
); );
}; };

View File

@ -24,6 +24,7 @@ import useOldLang from '../../../hooks/useOldLang';
import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji'; import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji';
import StickerSetCard from '../../common/StickerSetCard'; import StickerSetCard from '../../common/StickerSetCard';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -93,7 +94,7 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <Island>
<Checkbox <Checkbox
label={lang('SuggestStickers')} label={lang('SuggestStickers')}
checked={shouldSuggestStickers} checked={shouldSuggestStickers}
@ -101,7 +102,6 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
/> />
<ListItem <ListItem
narrow narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.CustomEmoji })} onClick={() => openSettingsScreen({ screen: SettingsScreens.CustomEmoji })}
icon="smile" icon="smile"
> >
@ -112,7 +112,6 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
<ListItem <ListItem
className="SettingsDefaultReaction" className="SettingsDefaultReaction"
narrow narrow
onClick={() => openSettingsScreen({ screen: SettingsScreens.QuickReaction })} onClick={() => openSettingsScreen({ screen: SettingsScreens.QuickReaction })}
> >
<ReactionStaticEmoji <ReactionStaticEmoji
@ -124,40 +123,44 @@ const SettingsStickers: FC<OwnProps & StateProps> = ({
<div className="title">{lang('DoubleTapSetting')}</div> <div className="title">{lang('DoubleTapSetting')}</div>
</ListItem> </ListItem>
)} )}
</div> </Island>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('InstalledStickers.DynamicPackOrder')} {lang('InstalledStickers.DynamicPackOrder')}
</h4> </IslandTitle>
<Island>
<Checkbox <Checkbox
label={lang('InstalledStickers.DynamicPackOrder')} label={lang('InstalledStickers.DynamicPackOrder')}
checked={shouldUpdateStickerSetOrder} checked={shouldUpdateStickerSetOrder}
onCheck={handleSuggestStickerSetOrderChange} onCheck={handleSuggestStickerSetOrderChange}
/> />
<p className="settings-item-description mt-3" dir="auto"> </Island>
{lang('InstalledStickers.DynamicPackOrderInfo')} <IslandDescription dir="auto">
</p> {lang('InstalledStickers.DynamicPackOrderInfo')}
</div> </IslandDescription>
{stickerSets && ( {stickerSets && (
<div className="settings-item"> <>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('ChooseStickerMyStickerSets')} {lang('ChooseStickerMyStickerSets')}
</h4> </IslandTitle>
<div ref={stickerSettingsRef}> <Island>
{stickerSets.map((stickerSet: ApiStickerSet) => ( <div ref={stickerSettingsRef}>
<StickerSetCard {stickerSets.map((stickerSet: ApiStickerSet) => (
key={stickerSet.id} <StickerSetCard
stickerSet={stickerSet} key={stickerSet.id}
observeIntersection={observeIntersectionForCovers} stickerSet={stickerSet}
onClick={handleStickerSetClick} observeIntersection={observeIntersectionForCovers}
noPlay={!canPlayAnimatedEmojis} onClick={handleStickerSetClick}
/> noPlay={!canPlayAnimatedEmojis}
))} />
</div> ))}
<p className="settings-item-description mt-3" dir="auto"> </div>
</Island>
<IslandDescription dir="auto">
{renderText(lang('StickersBotInfo'), ['links'])} {renderText(lang('StickersBotInfo'), ['links'])}
</p> </IslandDescription>
</div> </>
)} )}
</div> </div>
); );

View File

@ -123,7 +123,6 @@
gap: 0.75rem; gap: 0.75rem;
align-items: center; align-items: center;
margin-inline: 1rem;
padding: 0.125rem; padding: 0.125rem;
} }
@ -224,11 +223,19 @@
background-color: var(--accent-color); background-color: var(--accent-color);
} }
.settings-folders-header.settings-content-header {
padding-inline: 0;
}
.settings-folders-input-container { .settings-folders-input-container {
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
align-self: stretch; align-self: stretch;
.input-group {
margin: 0.5rem;
}
} }
.settings-folders-input-with-icon .form-control { .settings-folders-input-with-icon .form-control {
@ -236,11 +243,11 @@
} }
.settings-folders-icon-picker { .settings-folders-icon-picker {
--custom-emoji-size: 2rem; --custom-emoji-size: 1.5rem;
position: absolute; position: absolute;
inset-inline-end: 0.5rem; inset-inline-end: 1.5rem;
font-size: 2rem; font-size: 1.5rem;
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }

View File

@ -137,7 +137,7 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
} }
return ( return (
<div className="Picker settings-folders-chat-list"> <div className="Picker settings-folders-chat-list settings-picker-islands">
<PeerPicker <PeerPicker
categories={shouldHideChatTypes ? undefined : chatTypes} categories={shouldHideChatTypes ? undefined : chatTypes}
itemIds={displayedIds} itemIds={displayedIds}
@ -150,6 +150,7 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps> = ({
isSearchable isSearchable
withDefaultPadding withDefaultPadding
withPeerTypes withPeerTypes
withIslands
allowMultiple allowMultiple
itemInputType="checkbox" itemInputType="checkbox"
onSelectedIdsChange={handleSelectedIdsChange} onSelectedIdsChange={handleSelectedIdsChange}

View File

@ -40,6 +40,8 @@ import FolderIcon from '../../../common/FolderIcon';
import GroupChatInfo from '../../../common/GroupChatInfo'; import GroupChatInfo from '../../../common/GroupChatInfo';
import Icon from '../../../common/icons/Icon'; import Icon from '../../../common/icons/Icon';
import PrivateChatInfo from '../../../common/PrivateChatInfo'; import PrivateChatInfo from '../../../common/PrivateChatInfo';
import Island, { IslandDescription, IslandOutside, IslandTitle } from '../../../gili/layout/Island';
import Surface from '../../../gili/layout/Surface';
import FloatingActionButton from '../../../ui/FloatingActionButton'; import FloatingActionButton from '../../../ui/FloatingActionButton';
import InputText from '../../../ui/InputText'; import InputText from '../../../ui/InputText';
import ListItem from '../../../ui/ListItem'; import ListItem from '../../../ui/ListItem';
@ -381,8 +383,8 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
return ( return (
<div className="settings-fab-wrapper"> <div className="settings-fab-wrapper">
<div className="settings-content no-border custom-scroll"> <Surface scrollable className="settings-content no-border">
<div className="settings-content-header"> <IslandOutside className="settings-content-header settings-folders-header">
<AnimatedIconWithPreview <AnimatedIconWithPreview
size={STICKER_SIZE_FOLDER_SETTINGS} size={STICKER_SIZE_FOLDER_SETTINGS}
tgsUrl={LOCAL_TGS_URLS.FoldersNew} tgsUrl={LOCAL_TGS_URLS.FoldersNew}
@ -395,9 +397,9 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
{lang('FilterIncludeInfo')} {lang('FilterIncludeInfo')}
</p> </p>
)} )}
<div className="settings-folders-input-container"> <Island className="settings-folders-input-container">
<InputText <InputText
className={buildClassName('mb-0', !isMobile && 'settings-folders-input-with-icon')} className={buildClassName(!isMobile && 'settings-folders-input-with-icon')}
label={lang('FilterNameHint')} label={lang('FilterNameHint')}
value={titleText} value={titleText}
maxLength={folderTitleMaxLength} maxLength={folderTitleMaxLength}
@ -424,67 +426,69 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
/> />
</div> </div>
)} )}
</div> </Island>
</div> </IslandOutside>
{!isOnlyInvites && ( {!isOnlyInvites && (
<div className="settings-item"> <>
{state.error && state.error === ERROR_NO_CHATS && ( {state.error && state.error === ERROR_NO_CHATS && (
<p className="settings-item-description color-danger mb-2" dir={lang.isRtl ? 'rtl' : undefined}> <IslandDescription className="color-danger" dir={lang.isRtl ? 'rtl' : undefined}>
{oldLang(state.error)} {oldLang(state.error)}
</p> </IslandDescription>
)} )}
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterInclude')}</h4> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterInclude')}</IslandTitle>
<Island>
<ListItem
className="settings-folders-list-item color-primary"
icon="add"
narrow
onClick={onAddIncludedChats}
>
{lang('FilterAddChats')}
</ListItem>
<ListItem {renderChats('included')}
className="settings-folders-list-item color-primary" </Island>
icon="add" </>
narrow
onClick={onAddIncludedChats}
>
{lang('FilterAddChats')}
</ListItem>
{renderChats('included')}
</div>
)} )}
{!isOnlyInvites && !isEditingChatList && ( {!isOnlyInvites && !isEditingChatList && (
<div className="settings-item pt-3"> <>
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterExclude')}</h4> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterExclude')}</IslandTitle>
<Island>
<ListItem
className="settings-folders-list-item color-primary"
icon="add"
narrow
onClick={onAddExcludedChats}
>
{lang('FilterAddChats')}
</ListItem>
<ListItem {renderChats('excluded')}
className="settings-folders-list-item color-primary" </Island>
icon="add" </>
narrow
onClick={onAddExcludedChats}
>
{lang('FilterAddChats')}
</ListItem>
{renderChats('excluded')}
</div>
)} )}
<div className="settings-item pt-3"> <IslandTitle className="color-picker-header" dir={lang.isRtl ? 'rtl' : undefined}>
<h4 className="settings-item-header mb-3 color-picker-header" dir={lang.isRtl ? 'rtl' : undefined}> <span className="color-picker-title-text">{lang('FilterColorTitle')}</span>
<span className="color-picker-title-text">{lang('FilterColorTitle')}</span> <div className={buildClassName(
<div className={buildClassName( 'color-picker-title',
'color-picker-title', 'color-picker-selected-color',
'color-picker-selected-color', isCurrentUserPremium && state.folder.color !== undefined && state.folder.color !== -1
isCurrentUserPremium && state.folder.color !== undefined && state.folder.color !== -1 ? getPeerColorClass(state.folder.color)
? getPeerColorClass(state.folder.color) : 'color-picker-item-disabled',
: 'color-picker-item-disabled', )}
)} >
> {renderTextWithEntities({
{renderTextWithEntities({ text: state.folder.title.text,
text: state.folder.title.text, entities: state.folder.title.entities,
entities: state.folder.title.entities, noCustomEmojiPlayback: state.folder.noTitleAnimations,
noCustomEmojiPlayback: state.folder.noTitleAnimations, })}
})} </div>
</div> </IslandTitle>
</h4> <Island>
<div className="color-picker custom-scroll-x"> <div className="color-picker custom-scroll-x">
{FOLDER_COLORS.map((color) => ( {FOLDER_COLORS.map((color) => (
<button <button
@ -530,16 +534,15 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
)} )}
</button> </button>
</div> </div>
<p className="settings-item-description mb-0 mt-3" dir={lang.isRtl ? 'rtl' : undefined}> </Island>
{lang('FilterColorHint')} <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
</p> {lang('FilterColorHint')}
</div> </IslandDescription>
<div className="settings-item pt-3">
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
{oldLang('FolderLinkScreen.Title')}
</h4>
<IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{oldLang('FolderLinkScreen.Title')}
</IslandTitle>
<Island>
<ListItem <ListItem
className="settings-folders-list-item color-primary" className="settings-folders-list-item color-primary"
icon="add" icon="add"
@ -564,9 +567,8 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
</span> </span>
</ListItem> </ListItem>
))} ))}
</Island>
</div> </Surface>
</div>
<FloatingActionButton <FloatingActionButton
isShown={Boolean(state.isTouched)} isShown={Boolean(state.isTouched)}

View File

@ -28,6 +28,8 @@ import usePreviousDeprecated from '../../../../hooks/usePreviousDeprecated';
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview'; import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
import Icon from '../../../common/icons/Icon'; import Icon from '../../../common/icons/Icon';
import Island, { IslandOutside, IslandTitle } from '../../../gili/layout/Island';
import Surface from '../../../gili/layout/Surface';
import Button from '../../../ui/Button'; import Button from '../../../ui/Button';
import Checkbox from '../../../ui/Checkbox'; import Checkbox from '../../../ui/Checkbox';
import Draggable from '../../../ui/Draggable'; import Draggable from '../../../ui/Draggable';
@ -225,8 +227,8 @@ const SettingsFoldersMain = ({
}, [foldersById, isPremium, maxFolders]); }, [foldersById, isPremium, maxFolders]);
return ( return (
<div className="settings-content no-border custom-scroll"> <Surface scrollable className="settings-content no-border">
<div className="settings-content-header"> <IslandOutside className="settings-content-header">
<AnimatedIconWithPreview <AnimatedIconWithPreview
size={STICKER_SIZE_FOLDER_SETTINGS} size={STICKER_SIZE_FOLDER_SETTINGS}
tgsUrl={LOCAL_TGS_URLS.FoldersAll} tgsUrl={LOCAL_TGS_URLS.FoldersAll}
@ -250,11 +252,10 @@ const SettingsFoldersMain = ({
{lang('CreateNewFilter')} {lang('CreateNewFilter')}
</Button> </Button>
)} )}
</div> </IslandOutside>
<div className="settings-item pt-3">
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>{lang('Filters')}</h4>
<IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('Filters')}</IslandTitle>
<Island>
<div className="settings-sortable-container" style={`height: ${(folderIds?.length || 0) * FOLDER_HEIGHT_PX}px`}> <div className="settings-sortable-container" style={`height: ${(folderIds?.length || 0) * FOLDER_HEIGHT_PX}px`}>
{userFolders?.length ? userFolders.map((folder, i) => { {userFolders?.length ? userFolders.map((folder, i) => {
const isBlocked = i > maxFolders - 1; const isBlocked = i > maxFolders - 1;
@ -365,48 +366,49 @@ const SettingsFoldersMain = ({
</p> </p>
) : <Loading />} ) : <Loading />}
</div> </div>
</div> </Island>
{(recommendedChatFolders && Boolean(recommendedChatFolders.length)) && ( {(recommendedChatFolders && Boolean(recommendedChatFolders.length)) && (
<div className="settings-item pt-3"> <>
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('FilterRecommended')} {lang('FilterRecommended')}
</h4> </IslandTitle>
<Island>
{recommendedChatFolders.map((folder) => (
<ListItem
key={folder.id}
narrow
onClick={() => handleCreateFolderFromRecommended(folder)}
>
<div className="settings-folders-recommended-item">
<div className="multiline-item">
<span className="title">
{renderTextWithEntities({
text: folder.title.text,
entities: folder.title.entities,
noCustomEmojiPlayback: folder.noTitleAnimations,
})}
</span>
<span className="subtitle">{folder.description}</span>
</div>
{recommendedChatFolders.map((folder) => ( <Button
<ListItem className="px-3"
narrow color="primary"
size="tiny"
onClick={() => handleCreateFolderFromRecommended(folder)} pill
> fluid
<div className="settings-folders-recommended-item"> isRtl={lang.isRtl}
<div className="multiline-item"> >
<span className="title"> {lang('Add')}
{renderTextWithEntities({ </Button>
text: folder.title.text,
entities: folder.title.entities,
noCustomEmojiPlayback: folder.noTitleAnimations,
})}
</span>
<span className="subtitle">{folder.description}</span>
</div> </div>
</ListItem>
<Button ))}
className="px-3" </Island>
color="primary" </>
size="tiny"
pill
fluid
isRtl={lang.isRtl}
>
{lang('Add')}
</Button>
</div>
</ListItem>
))}
</div>
)} )}
<div className="settings-item pt-3"> <Island>
<div className="settings-item-relative"> <div className="settings-item-relative">
<Checkbox <Checkbox
label={lang('ShowFolderTags')} label={lang('ShowFolderTags')}
@ -422,26 +424,27 @@ const SettingsFoldersMain = ({
/> />
{!isPremium && <Icon name="lock-badge" className="settings-folders-lock-icon" />} {!isPremium && <Icon name="lock-badge" className="settings-folders-lock-icon" />}
</div> </div>
</div> </Island>
{!isMobile && ( {!isMobile && (
<div className="settings-item pt-3"> <>
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>{lang('TabsPosition')}</h4> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang('TabsPosition')}</IslandTitle>
<Island>
<RadioGroup <RadioGroup
name="tabsPosition" name="tabsPosition"
options={[{ options={[{
label: lang('TabsPositionLeft'), label: lang('TabsPositionLeft'),
value: FOLDERS_POSITION_LEFT, value: FOLDERS_POSITION_LEFT,
}, { }, {
label: lang('TabsPositionTop'), label: lang('TabsPositionTop'),
value: FOLDERS_POSITION_TOP, value: FOLDERS_POSITION_TOP,
}]} }]}
selected={foldersPosition} selected={foldersPosition}
onChange={handleFoldersPositionChange} onChange={handleFoldersPositionChange}
/> />
</div> </Island>
</>
)} )}
</div> </Surface>
); );
}; };

View File

@ -26,6 +26,7 @@ import useOldLang from '../../../../hooks/useOldLang';
import AnimatedIcon from '../../../common/AnimatedIcon'; import AnimatedIcon from '../../../common/AnimatedIcon';
import LinkField from '../../../common/LinkField'; import LinkField from '../../../common/LinkField';
import PeerPicker from '../../../common/pickers/PeerPicker'; import PeerPicker from '../../../common/pickers/PeerPicker';
import Island, { IslandTitle } from '../../../gili/layout/Island';
import FloatingActionButton from '../../../ui/FloatingActionButton'; import FloatingActionButton from '../../../ui/FloatingActionButton';
type OwnProps = { type OwnProps = {
@ -170,15 +171,18 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
)} )}
</div> </div>
<LinkField <IslandTitle>{oldLang('InviteLink.InviteLink')}</IslandTitle>
className="settings-item" <Island>
link={!url ? oldLang('Loading') : url} <LinkField
withShare link={!url ? oldLang('Loading') : url}
onRevoke={handleRevoke} withShare
isDisabled={!chatsCount || isTouched} noTitle
/> onRevoke={handleRevoke}
isDisabled={!chatsCount || isTouched}
/>
</Island>
<div className="settings-item settings-item-picker"> <Island>
<PeerPicker <PeerPicker
itemIds={itemIds} itemIds={itemIds}
lockedUnselectedIds={lockedIds} lockedUnselectedIds={lockedIds}
@ -189,7 +193,7 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
withStatus withStatus
itemInputType="checkbox" itemInputType="checkbox"
/> />
</div> </Island>
<FloatingActionButton <FloatingActionButton
isShown={isLoading || isTouched} isShown={isLoading || isTouched}

View File

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

View File

@ -10,6 +10,7 @@ import useHistoryBack from '../../../../hooks/useHistoryBack';
import useOldLang from '../../../../hooks/useOldLang'; import useOldLang from '../../../../hooks/useOldLang';
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview'; import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
import Island from '../../../gili/layout/Island';
import ListItem from '../../../ui/ListItem'; import ListItem from '../../../ui/ListItem';
import lockPreviewUrl from '../../../../assets/lock.png'; import lockPreviewUrl from '../../../../assets/lock.png';
@ -42,7 +43,7 @@ const SettingsPasscodeEnabled: FC<OwnProps> = ({
</p> </p>
</div> </div>
<div className="settings-item"> <Island>
<ListItem <ListItem
icon="edit" icon="edit"
@ -57,7 +58,7 @@ const SettingsPasscodeEnabled: FC<OwnProps> = ({
> >
{lang('Passcode.TurnOff')} {lang('Passcode.TurnOff')}
</ListItem> </ListItem>
</div> </Island>
</div> </div>
); );
}; };

View File

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

View File

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

View File

@ -14,6 +14,7 @@ import useHistoryBack from '../../../../hooks/useHistoryBack';
import useOldLang from '../../../../hooks/useOldLang'; import useOldLang from '../../../../hooks/useOldLang';
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker'; import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
import Island from '../../../gili/layout/Island';
import InputText from '../../../ui/InputText'; import InputText from '../../../ui/InputText';
import Loading from '../../../ui/Loading'; import Loading from '../../../ui/Loading';
@ -88,7 +89,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
)} )}
</div> </div>
<div className="settings-item settings-group"> <Island>
<InputText <InputText
value={value} value={value}
ref={inputRef} ref={inputRef}
@ -98,7 +99,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
onChange={handleInputChange} onChange={handleInputChange}
/> />
{isLoading && <Loading />} {isLoading && <Loading />}
</div> </Island>
</div> </div>
); );
}; };

View File

@ -11,6 +11,7 @@ import useHistoryBack from '../../../../hooks/useHistoryBack';
import useOldLang from '../../../../hooks/useOldLang'; import useOldLang from '../../../../hooks/useOldLang';
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview'; import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
import Island from '../../../gili/layout/Island';
import ListItem from '../../../ui/ListItem'; import ListItem from '../../../ui/ListItem';
import lockPreviewUrl from '../../../../assets/lock.png'; import lockPreviewUrl from '../../../../assets/lock.png';
@ -46,7 +47,7 @@ const SettingsTwoFaEnabled: FC<OwnProps> = ({
</p> </p>
</div> </div>
<div className="settings-item"> <Island>
<ListItem <ListItem
icon="edit" icon="edit"
@ -68,7 +69,7 @@ const SettingsTwoFaEnabled: FC<OwnProps> = ({
> >
{lang('SetRecoveryEmail')} {lang('SetRecoveryEmail')}
</ListItem> </ListItem>
</div> </Island>
</div> </div>
); );
}; };

View File

@ -16,6 +16,7 @@ import useHistoryBack from '../../../../hooks/useHistoryBack';
import useOldLang from '../../../../hooks/useOldLang'; import useOldLang from '../../../../hooks/useOldLang';
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker'; import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
import Island from '../../../gili/layout/Island';
import Button from '../../../ui/Button'; import Button from '../../../ui/Button';
import InputText from '../../../ui/InputText'; import InputText from '../../../ui/InputText';
import Modal from '../../../ui/Modal'; import Modal from '../../../ui/Modal';
@ -107,8 +108,8 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
)} )}
</div> </div>
<div className="settings-item settings-group"> <Island>
<form action="" onSubmit={handleSubmit}> <form className="settings-input" action="" onSubmit={handleSubmit}>
<InputText <InputText
ref={inputRef} ref={inputRef}
value={value} value={value}
@ -161,7 +162,7 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
</div> </div>
</Modal> </Modal>
)} )}
</div> </Island>
</div> </div>
); );
}; };

View File

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

View File

@ -1,11 +1,17 @@
.AddChatMembers { .AddChatMembers {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
height: 100%; height: 100%;
background: var(--color-background); padding: 1rem;
background: var(--color-background-secondary);
&-inner { &-inner {
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
height: 100%; height: 100%;
} }
} }

View File

@ -131,6 +131,7 @@ const AddChatMembers: FC<OwnProps & StateProps> = ({
onFilterChange={handleFilterChange} onFilterChange={handleFilterChange}
isSearchable isSearchable
withDefaultPadding withDefaultPadding
withIslands
noScrollRestore={noPickerScrollRestore} noScrollRestore={noPickerScrollRestore}
allowMultiple allowMultiple
withStatus withStatus

View File

@ -20,6 +20,7 @@ import useOldLang from '../../hooks/useOldLang';
import CustomEmojiPicker from '../common/CustomEmojiPicker'; import CustomEmojiPicker from '../common/CustomEmojiPicker';
import TopicIcon from '../common/TopicIcon'; import TopicIcon from '../common/TopicIcon';
import Island from '../gili/layout/Island';
import FloatingActionButton from '../ui/FloatingActionButton'; import FloatingActionButton from '../ui/FloatingActionButton';
import InputText from '../ui/InputText'; import InputText from '../ui/InputText';
import Transition from '../ui/Transition'; import Transition from '../ui/Transition';
@ -114,7 +115,7 @@ const CreateTopic: FC<OwnProps & StateProps> = ({
return ( return (
<div className={styles.root}> <div className={styles.root}>
<div className={buildClassName(styles.content, 'custom-scroll')}> <div className={buildClassName(styles.content, 'custom-scroll')}>
<div className={buildClassName(styles.section, styles.top)}> <Island className={styles.top}>
<span className={styles.heading}>{lang('CreateTopicTitle')}</span> <span className={styles.heading}>{lang('CreateTopicTitle')}</span>
<Transition <Transition
name="zoomFade" name="zoomFade"
@ -138,8 +139,8 @@ const CreateTopic: FC<OwnProps & StateProps> = ({
disabled={isLoading} disabled={isLoading}
teactExperimentControlled teactExperimentControlled
/> />
</div> </Island>
<div className={buildClassName(styles.section, styles.bottom)}> <Island className={styles.bottom}>
<CustomEmojiPicker <CustomEmojiPicker
idPrefix="create-topic-icons-set-" idPrefix="create-topic-icons-set-"
isHidden={!isActive} isHidden={!isActive}
@ -149,7 +150,7 @@ const CreateTopic: FC<OwnProps & StateProps> = ({
pickerListClassName="fab-padding-bottom" pickerListClassName="fab-padding-bottom"
withDefaultTopicIcons withDefaultTopicIcons
/> />
</div> </Island>
</div> </div>
<FloatingActionButton <FloatingActionButton
isShown={isTouched} isShown={isTouched}

View File

@ -20,6 +20,7 @@ import useOldLang from '../../hooks/useOldLang';
import CustomEmojiPicker from '../common/CustomEmojiPicker'; import CustomEmojiPicker from '../common/CustomEmojiPicker';
import TopicIcon from '../common/TopicIcon'; import TopicIcon from '../common/TopicIcon';
import Island from '../gili/layout/Island';
import FloatingActionButton from '../ui/FloatingActionButton'; import FloatingActionButton from '../ui/FloatingActionButton';
import InputText from '../ui/InputText'; import InputText from '../ui/InputText';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
@ -136,7 +137,7 @@ const EditTopic: FC<OwnProps & StateProps> = ({
{!topic && <Loading />} {!topic && <Loading />}
{topic && ( {topic && (
<> <>
<div className={buildClassName(styles.section, styles.top, isGeneral && styles.general)}> <Island className={buildClassName(styles.top, isGeneral && styles.general)}>
<span className={styles.heading}>{lang(isGeneral ? 'CreateGeneralTopicTitle' : 'CreateTopicTitle')}</span> <span className={styles.heading}>{lang(isGeneral ? 'CreateGeneralTopicTitle' : 'CreateTopicTitle')}</span>
<Transition <Transition
name="zoomFade" name="zoomFade"
@ -160,9 +161,9 @@ const EditTopic: FC<OwnProps & StateProps> = ({
disabled={isLoading} disabled={isLoading}
teactExperimentControlled teactExperimentControlled
/> />
</div> </Island>
{!isGeneral && ( {!isGeneral && (
<div className={buildClassName(styles.section, styles.bottom)}> <Island className={styles.bottom}>
<CustomEmojiPicker <CustomEmojiPicker
idPrefix="edit-topic-icons-set-" idPrefix="edit-topic-icons-set-"
isHidden={!isActive} isHidden={!isActive}
@ -172,7 +173,7 @@ const EditTopic: FC<OwnProps & StateProps> = ({
pickerListClassName="fab-padding-bottom" pickerListClassName="fab-padding-bottom"
withDefaultTopicIcons withDefaultTopicIcons
/> />
</div> </Island>
)} )}
</> </>
)} )}

View File

@ -5,37 +5,35 @@
position: relative; position: relative;
height: 100%; height: 100%;
background-color: var(--color-background); background-color: var(--color-background-secondary);
} }
.content { .content {
overflow: auto; overflow: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} padding: 1rem;
.section {
display: flex;
flex-direction: column;
justify-content: center;
@include mixins.side-panel-section;
} }
.general { .general {
border-bottom: 0;
box-shadow: none; box-shadow: none;
} }
.top { .top {
display: flex;
flex-direction: column;
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
} }
.bottom { .bottom {
display: flex;
flex-direction: column;
flex-grow: 1; flex-grow: 1;
min-height: 30rem; min-height: 30rem;
margin-bottom: 0; padding-bottom: 0;
} }
.iconWrapper { .iconWrapper {

View File

@ -7,7 +7,7 @@
height: 100%; height: 100%;
:global(.FloatingActionButton) { :global(.FloatingActionButton) {
z-index: 1; z-index: 3;
@include mixins.with-vt-type('rightColumn'); @include mixins.with-vt-type('rightColumn');

View File

@ -1267,7 +1267,6 @@ const Profile = ({
chatOrUserId={profileId} chatOrUserId={profileId}
isSavedDialog={isSavedDialog} isSavedDialog={isSavedDialog}
isOwnProfile={isOwnProfile} isOwnProfile={isOwnProfile}
withIslands
className={styles.chatExtraBlock} className={styles.chatExtraBlock}
style={createVtnStyle('chatExtraBlock', true)} style={createVtnStyle('chatExtraBlock', true)}
/> />

View File

@ -20,6 +20,8 @@
} }
> .Transition { > .Transition {
--slide-background-color: var(--color-background-secondary);
overflow: hidden; overflow: hidden;
height: calc(100% - var(--header-height)); height: calc(100% - var(--header-height));
} }
@ -33,7 +35,9 @@
} }
} }
.Management .section > .ChatInfo { .Management .section > .ChatInfo,
.Management > .custom-scroll > .ChatInfo,
.Management > .panel-content > .ChatInfo {
margin: 1rem 0; margin: 1rem 0;
padding: 0 1.5rem; padding: 0 1.5rem;
text-align: center; text-align: center;

View File

@ -1,11 +1,11 @@
import type { FC } from '@teact'; import type { FC } from '@teact';
import { memo, useEffect, useRef, useState } from '@teact'; import { memo, useEffect, useLayoutEffect, useRef, useState } from '@teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { AnimationLevel, ThreadId } from '../../types'; import type { AnimationLevel, ThreadId } from '../../types';
import { ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent } from '../../types'; import { ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent } from '../../types';
import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config'; import { MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
import { getIsSavedDialog } from '../../global/helpers'; import { getIsSavedDialog } from '../../global/helpers';
import { import {
selectAreActiveChatsLoaded, selectAreActiveChatsLoaded,
@ -20,10 +20,12 @@ import captureEscKeyListener from '../../util/captureEscKeyListener';
import { resolveTransitionName } from '../../util/resolveTransitionName.ts'; import { resolveTransitionName } from '../../util/resolveTransitionName.ts';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useFlag from '../../hooks/useFlag';
import useHistoryBack from '../../hooks/useHistoryBack'; import useHistoryBack from '../../hooks/useHistoryBack';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
import useScrollNotch from '../../hooks/useScrollNotch.ts'; import useScrollNotch from '../../hooks/useScrollNotch.ts';
import useSyncEffect from '../../hooks/useSyncEffect';
import useWindowSize from '../../hooks/window/useWindowSize'; import useWindowSize from '../../hooks/window/useWindowSize';
import Transition from '../ui/Transition'; import Transition from '../ui/Transition';
@ -63,7 +65,6 @@ type StateProps = {
hasProfileBackground?: boolean; hasProfileBackground?: boolean;
}; };
const ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
const MAIN_SCREENS_COUNT = Object.keys(RightColumnContent).length / 2; const MAIN_SCREENS_COUNT = Object.keys(RightColumnContent).length / 2;
const MANAGEMENT_SCREENS_COUNT = Object.keys(ManagementScreens).length / 2; const MANAGEMENT_SCREENS_COUNT = Object.keys(ManagementScreens).length / 2;
@ -120,7 +121,6 @@ const RightColumn: FC<OwnProps & StateProps> = ({
const [selectedChatMemberId, setSelectedChatMemberId] = useState<string | undefined>(); const [selectedChatMemberId, setSelectedChatMemberId] = useState<string | undefined>();
const [isPromotedByCurrentUser, setIsPromotedByCurrentUser] = useState<boolean | undefined>(); const [isPromotedByCurrentUser, setIsPromotedByCurrentUser] = useState<boolean | undefined>();
const [isProfileExpanded, setIsProfileExpanded] = useState(false); const [isProfileExpanded, setIsProfileExpanded] = useState(false);
const [isProfileScrolled, setIsProfileScrolled] = useState(false);
const isScrolledDown = profileState !== ProfileState.Profile; const isScrolledDown = profileState !== ProfileState.Profile;
const isOpen = contentKey !== undefined; const isOpen = contentKey !== undefined;
@ -138,43 +138,57 @@ const RightColumn: FC<OwnProps & StateProps> = ({
const isCreatingTopic = contentKey === RightColumnContent.CreateTopic; const isCreatingTopic = contentKey === RightColumnContent.CreateTopic;
const isEditingTopic = contentKey === RightColumnContent.EditTopic; const isEditingTopic = contentKey === RightColumnContent.EditTopic;
const isOverlaying = windowWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN; const isOverlaying = windowWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN;
const isContentScrolledRef = useRef(false);
const headerBackground: 'regular' | 'secondary' = (() => { const hasStaticHeader = isSavedMessages || isAddingChatMembers || isCreatingTopic || isEditingTopic
if (isSavedMessages) return 'secondary'; || (isManagement && (
if (!isProfile) return 'regular'; managementScreen === ManagementScreens.GroupMembers
|| managementScreen === ManagementScreens.ChatAdministrators
|| managementScreen === ManagementScreens.GroupAddAdmins
));
const getHeaderBackground = useLastCallback((): 'regular' | 'secondary' => {
const isScrolled = isContentScrolledRef.current;
if (hasStaticHeader) return 'secondary';
if (!isProfile) return isScrolled ? 'regular' : 'secondary';
if (isScrolledDown) return 'secondary'; if (isScrolledDown) return 'secondary';
if (!isProfileScrolled && !isProfileExpanded && !hasProfileBackground) return 'secondary'; if (!isScrolled && !isProfileExpanded && !hasProfileBackground) return 'secondary';
return 'regular'; return 'regular';
})(); });
const [shouldSkipTransition, setShouldSkipTransition] = useState(!isOpen); const [headerBackground, setHeaderBackground] = useState<'regular' | 'secondary'>(getHeaderBackground);
const handleContentScrolled = useLastCallback((isScrolled: boolean) => {
isContentScrolledRef.current = isScrolled;
setHeaderBackground(getHeaderBackground());
});
const [isAnimating, startAnimating, stopAnimating] = useFlag();
useLayoutEffect(() => {
const elements = containerRef.current?.querySelectorAll<HTMLElement>(
':scope .custom-scroll, :scope .panel-content',
);
elements?.forEach((el) => {
el.scrollTop = 0;
});
isContentScrolledRef.current = false;
setHeaderBackground(getHeaderBackground());
}, [contentKey, managementScreen]);
useSyncEffect(() => {
setHeaderBackground(getHeaderBackground());
}, [isScrolledDown, isProfileExpanded, hasProfileBackground]);
const renderingContentKey = useCurrentOrPrev(contentKey, true, !isChatSelected) ?? -1; const renderingContentKey = useCurrentOrPrev(contentKey, true, !isChatSelected) ?? -1;
useScrollNotch({ useScrollNotch({
containerRef, containerRef,
selector: ':scope .custom-scroll, :scope .panel-content', selector: ':scope .custom-scroll, :scope .panel-content',
shouldHideTopNotch: isSavedMessages || (isProfile && isScrolledDown), shouldHideTopNotch: isAnimating || isSavedMessages || hasStaticHeader || (isProfile && isScrolledDown),
onScrolled: handleContentScrolled,
}, [contentKey, managementScreen, chatId, threadId]); }, [contentKey, managementScreen, chatId, threadId]);
useEffect(() => {
if (!isProfile || isScrolledDown || isSavedMessages) {
setIsProfileScrolled(false);
return undefined;
}
const scrollEl = containerRef.current?.querySelector<HTMLElement>('.custom-scroll');
if (!scrollEl) return undefined;
const handleProfileScroll = () => {
setIsProfileScrolled(scrollEl.scrollTop > 1);
};
handleProfileScroll();
scrollEl.addEventListener('scroll', handleProfileScroll, { passive: true });
return () => scrollEl.removeEventListener('scroll', handleProfileScroll);
}, [chatId, threadId, isProfile, isScrolledDown, isSavedMessages]);
const close = useLastCallback((shouldScrollUp = true) => { const close = useLastCallback((shouldScrollUp = true) => {
switch (contentKey) { switch (contentKey) {
case RightColumnContent.AddingMembers: case RightColumnContent.AddingMembers:
@ -278,12 +292,6 @@ const RightColumn: FC<OwnProps & StateProps> = ({
useEffect(() => (isOpen && chatId ? captureEscKeyListener(close) : undefined), [isOpen, close, chatId]); useEffect(() => (isOpen && chatId ? captureEscKeyListener(close) : undefined), [isOpen, close, chatId]);
useEffect(() => {
setTimeout(() => {
setShouldSkipTransition(!isOpen);
}, ANIMATION_DURATION);
}, [isOpen]);
useEffect(() => { useEffect(() => {
if (nextManagementScreen) { if (nextManagementScreen) {
setManagementScreen(nextManagementScreen); setManagementScreen(nextManagementScreen);
@ -429,10 +437,12 @@ const RightColumn: FC<OwnProps & StateProps> = ({
/> />
<Transition <Transition
ref={containerRef} ref={containerRef}
name={resolveTransitionName('layers', animationLevel, shouldSkipTransition || shouldSkipHistoryAnimations)} name={resolveTransitionName('layers', animationLevel, !isOpen || shouldSkipHistoryAnimations)}
renderCount={MAIN_SCREENS_COUNT + MANAGEMENT_SCREENS_COUNT} renderCount={MAIN_SCREENS_COUNT + MANAGEMENT_SCREENS_COUNT}
activeKey={isManagement ? MAIN_SCREENS_COUNT + managementScreen : renderingContentKey} activeKey={isManagement ? MAIN_SCREENS_COUNT + managementScreen : renderingContentKey}
shouldCleanup shouldCleanup
onStart={startAnimating}
onStop={stopAnimating}
cleanupExceptionKey={ cleanupExceptionKey={
(renderingContentKey === RightColumnContent.MessageStatistics (renderingContentKey === RightColumnContent.MessageStatistics
|| renderingContentKey === RightColumnContent.StoryStatistics) || renderingContentKey === RightColumnContent.StoryStatistics)

View File

@ -26,6 +26,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia'; import useMedia from '../../../hooks/useMedia';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island, { IslandDescription } from '../../gili/layout/Island';
import AvatarEditable from '../../ui/AvatarEditable'; import AvatarEditable from '../../ui/AvatarEditable';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import InputText from '../../ui/InputText'; import InputText from '../../ui/InputText';
@ -183,7 +184,7 @@ const ManageBot: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="custom-scroll"> <div className="custom-scroll">
<div className="section"> <Island>
<AvatarEditable <AvatarEditable
currentAvatarBlobUrl={currentAvatarBlobUrl} currentAvatarBlobUrl={currentAvatarBlobUrl}
onChange={handlePhotoChange} onChange={handlePhotoChange}
@ -206,8 +207,8 @@ const ManageBot: FC<OwnProps & StateProps> = ({
maxLength={maxBioLength} maxLength={maxBioLength}
maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined} maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined}
/> />
</div> </Island>
<div className="section"> <Island>
<div className="dialog-buttons"> <div className="dialog-buttons">
<ListItem icon="bot-commands-filled" ripple onClick={handleChangeEditIntro}> <ListItem icon="bot-commands-filled" ripple onClick={handleChangeEditIntro}>
<span>{lang('BotEditIntro')}</span> <span>{lang('BotEditIntro')}</span>
@ -218,11 +219,11 @@ const ManageBot: FC<OwnProps & StateProps> = ({
<ListItem icon="bots" ripple onClick={handleChangeSettings}> <ListItem icon="bots" ripple onClick={handleChangeSettings}>
<span>{lang('BotChangeSettings')}</span> <span>{lang('BotChangeSettings')}</span>
</ListItem> </ListItem>
<div className="section-info section-info_push"> <IslandDescription>
{renderText(lang('BotManageInfo'), ['links'])} {renderText(lang('BotManageInfo'), ['links'])}
</div> </IslandDescription>
</div> </div>
</div> </Island>
</div> </div>
<FloatingActionButton <FloatingActionButton
isShown={isFieldTouched || isAvatarTouched} isShown={isFieldTouched || isAvatarTouched}

View File

@ -21,6 +21,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia'; import useMedia from '../../../hooks/useMedia';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island from '../../gili/layout/Island';
import AvatarEditable from '../../ui/AvatarEditable'; import AvatarEditable from '../../ui/AvatarEditable';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
@ -225,12 +226,12 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section"> <AvatarEditable
<AvatarEditable currentAvatarBlobUrl={currentAvatarBlobUrl}
currentAvatarBlobUrl={currentAvatarBlobUrl} onChange={handleSetPhoto}
onChange={handleSetPhoto} disabled={!canChangeInfo}
disabled={!canChangeInfo} />
/> <Island>
<div className="settings-edit"> <div className="settings-edit">
<InputText <InputText
id="channel-title" id="channel-title"
@ -318,8 +319,8 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
/> />
</ListItem> </ListItem>
)} )}
</div> </Island>
<div className="section"> <Island>
<ListItem <ListItem
icon="admin" icon="admin"
multiline multiline
@ -344,12 +345,12 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
<span className="title">{lang('ChannelBlockedUsers')}</span> <span className="title">{lang('ChannelBlockedUsers')}</span>
<span className="subtitle">{removedUsersCount}</span> <span className="subtitle">{removedUsersCount}</span>
</ListItem> </ListItem>
</div> </Island>
<div className="section"> <Island>
<ListItem icon="delete" ripple destructive onClick={openDeleteDialog}> <ListItem icon="delete" ripple destructive onClick={openDeleteDialog}>
{chat.isCreator ? lang('ChannelDelete') : lang('LeaveChannel')} {chat.isCreator ? lang('ChannelDelete') : lang('LeaveChannel')}
</ListItem> </ListItem>
</div> </Island>
</div> </div>
<FloatingActionButton <FloatingActionButton
isShown={isProfileFieldsTouched} isShown={isProfileFieldsTouched}

View File

@ -14,6 +14,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -111,7 +112,7 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section"> <Island>
<ListItem <ListItem
icon="recent" icon="recent"
multiline multiline
@ -120,15 +121,14 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
<span className="title">{lang('EventLog')}</span> <span className="title">{lang('EventLog')}</span>
<span className="subtitle">{lang(isChannel ? 'EventLogInfoDetailChannel' : 'EventLogInfoDetail')}</span> <span className="subtitle">{lang(isChannel ? 'EventLogInfoDetailChannel' : 'EventLogInfoDetail')}</span>
</ListItem> </ListItem>
</div> </Island>
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}>
<p className="section-help" dir="auto">
{lang(isChannel
? 'Channel.Management.AddModeratorHelp'
: 'Group.Management.AddModeratorHelp')}
</p>
<IslandDescription dir="auto">
{lang(isChannel
? 'Channel.Management.AddModeratorHelp'
: 'Group.Management.AddModeratorHelp')}
</IslandDescription>
<Island dir={lang.isRtl ? 'rtl' : undefined}>
{adminMembers.map((member) => ( {adminMembers.map((member) => (
<ListItem <ListItem
key={member.userId} key={member.userId}
@ -150,10 +150,10 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
ariaLabel={lang('Channel.Management.AddModerator')} ariaLabel={lang('Channel.Management.AddModerator')}
iconName="add-user-filled" iconName="add-user-filled"
/> />
</div> </Island>
{canToggleSignatures && ( {canToggleSignatures && (
<div className="section"> <Island>
<div className="ListItem narrow"> <div className="ListItem narrow">
<Checkbox <Checkbox
checked={areSignaturesEnabled} checked={areSignaturesEnabled}
@ -170,12 +170,12 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
onChange={handleToggleProfiles} onChange={handleToggleProfiles}
/> />
</div> </div>
<p className="section-info section-info_push"> <IslandDescription>
{lang('ChannelSignProfilesInfo')} {lang('ChannelSignProfilesInfo')}
</p> </IslandDescription>
</> </>
)} )}
</div> </Island>
)} )}
</div> </div>
</div> </div>

View File

@ -25,6 +25,7 @@ import LinkField from '../../common/LinkField';
import ManageUsernames from '../../common/ManageUsernames'; import ManageUsernames from '../../common/ManageUsernames';
import SafeLink from '../../common/SafeLink'; import SafeLink from '../../common/SafeLink';
import UsernameInput from '../../common/UsernameInput'; import UsernameInput from '../../common/UsernameInput';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -184,71 +185,77 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps> = ({
const purchaseInfoLink = `${TME_LINK_PREFIX}${PURCHASE_USERNAME}`; const purchaseInfoLink = `${TME_LINK_PREFIX}${PURCHASE_USERNAME}`;
return ( return (
<p className="section-info" dir="auto"> <IslandDescription dir="auto">
{(lang('lng_username_purchase_available')) {(lang('lng_username_purchase_available'))
.replace('{link}', '%PURCHASE_LINK%') .replace('{link}', '%PURCHASE_LINK%')
.split('%') .split('%')
.map((s) => { .map((s) => {
return (s === 'PURCHASE_LINK' ? <SafeLink url={purchaseInfoLink} text={`@${PURCHASE_USERNAME}`} /> : s); return (s === 'PURCHASE_LINK' ? <SafeLink url={purchaseInfoLink} text={`@${PURCHASE_USERNAME}`} /> : s);
})} })}
</p> </IslandDescription>
); );
} }
return ( return (
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>{lang(`${langPrefix2}Type`)}</IslandTitle>
<h3 className="section-heading">{lang(`${langPrefix2}Type`)}</h3> <Island dir={lang.isRtl ? 'rtl' : undefined}>
<RadioGroup <RadioGroup
selected={privacyType} selected={privacyType}
name="channel-type" name="channel-type"
options={options} options={options}
onChange={handleOptionChange} onChange={handleOptionChange}
/> />
</div> </Island>
{privacyType === 'private' ? ( {privacyType === 'private' ? (
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}> <>
{privateInviteLink ? ( <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<> {lang('InviteLink.InviteLink')}
<LinkField link={privateInviteLink} className="invite-link" /> </IslandTitle>
<p className="section-info" dir={lang.isRtl ? 'rtl' : undefined}> <Island dir={lang.isRtl ? 'rtl' : undefined}>
{lang(`${langPrefix1}PrivateLinkHelp`)} {privateInviteLink ? (
</p> <>
<LinkField link={privateInviteLink} className="invite-link" noTitle />
<ListItem icon="delete" ripple destructive onClick={openRevokeConfirmDialog}> <ListItem icon="delete" ripple destructive onClick={openRevokeConfirmDialog}>
{lang('RevokeLink')} {lang('RevokeLink')}
</ListItem> </ListItem>
<ConfirmDialog <ConfirmDialog
isOpen={isRevokeConfirmDialogOpen} isOpen={isRevokeConfirmDialogOpen}
onClose={closeRevokeConfirmDialog} onClose={closeRevokeConfirmDialog}
text={lang('RevokeAlert')} text={lang('RevokeAlert')}
confirmLabel={lang('RevokeButton')} confirmLabel={lang('RevokeButton')}
confirmHandler={handleRevokePrivateLink} confirmHandler={handleRevokePrivateLink}
confirmIsDestructive confirmIsDestructive
/> />
</> </>
) : ( ) : (
<Loading /> <Loading />
)} )}
</div> </Island>
<IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
{lang(`${langPrefix1}PrivateLinkHelp`)}
</IslandDescription>
</>
) : ( ) : (
<div className="section no-border"> <>
<div className="settings-input"> <Island>
<UsernameInput <div className="settings-input">
asLink <UsernameInput
currentUsername={currentUsername} asLink
isLoading={isLoading} currentUsername={currentUsername}
isUsernameAvailable={isUsernameAvailable} isLoading={isLoading}
checkedUsername={checkedUsername} isUsernameAvailable={isUsernameAvailable}
onChange={handleUsernameChange} checkedUsername={checkedUsername}
/> onChange={handleUsernameChange}
</div> />
</div>
</Island>
{error === USERNAME_PURCHASE_ERROR && renderPurchaseLink()} {error === USERNAME_PURCHASE_ERROR && renderPurchaseLink()}
<p className="section-info" dir="auto"> <IslandDescription dir="auto">
{lang(`${langPrefix2}.Username.CreatePublicLinkHelp`)} {lang(`${langPrefix2}.Username.CreatePublicLinkHelp`)}
</p> </IslandDescription>
</div> </>
)} )}
{shouldRenderUsernamesManage && ( {shouldRenderUsernamesManage && (
<ManageUsernames <ManageUsernames
@ -257,22 +264,22 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps> = ({
onEditUsername={handleUsernameChange} onEditUsername={handleUsernameChange}
/> />
)} )}
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
<h3 className="section-heading"> {lang(isChannel ? 'ChannelVisibility.Forwarding.ChannelTitle' : 'ChannelVisibility.Forwarding.GroupTitle')}
{lang(isChannel ? 'ChannelVisibility.Forwarding.ChannelTitle' : 'ChannelVisibility.Forwarding.GroupTitle')} </IslandTitle>
</h3> <Island dir={lang.isRtl ? 'rtl' : undefined}>
<RadioGroup <RadioGroup
selected={isProtected ? 'protected' : 'allowed'} selected={isProtected ? 'protected' : 'allowed'}
name="forwarding-type" name="forwarding-type"
options={forwardingOptions} options={forwardingOptions}
onChange={handleForwardingOptionChange} onChange={handleForwardingOptionChange}
/> />
<p className="section-info section-info_push"> </Island>
{isChannel <IslandDescription>
? lang('ChannelVisibility.Forwarding.ChannelInfo') {isChannel
: lang('ChannelVisibility.Forwarding.GroupInfo')} ? lang('ChannelVisibility.Forwarding.ChannelInfo')
</p> : lang('ChannelVisibility.Forwarding.GroupInfo')}
</div> </IslandDescription>
</div> </div>
<FloatingActionButton <FloatingActionButton
isShown={canUpdate} isShown={canUpdate}

View File

@ -13,6 +13,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription } from '../../gili/layout/Island';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import ListItem, { type MenuItemContextAction } from '../../ui/ListItem'; import ListItem, { type MenuItemContextAction } from '../../ui/ListItem';
import RemoveGroupUserModal from './RemoveGroupUserModal'; import RemoveGroupUserModal from './RemoveGroupUserModal';
@ -83,40 +84,41 @@ const ManageChatRemovedUsers: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section" dir={lang.isRtl ? 'rtl' : undefined}> <IslandDescription>{lang(isChannel ? 'NoBlockedChannel2' : 'NoBlockedGroup2')}</IslandDescription>
<p className="section-help">{lang(isChannel ? 'NoBlockedChannel2' : 'NoBlockedGroup2')}</p> {Boolean(removedMembers.length) && (
<Island dir={lang.isRtl ? 'rtl' : undefined}>
{removedMembers.map((member) => ( {removedMembers.map((member) => (
<ListItem <ListItem
key={member.userId} key={member.userId}
className="chat-item-clickable" className="chat-item-clickable"
ripple ripple
contextActions={getContextActions(member)} contextActions={getContextActions(member)}
> >
<PrivateChatInfo <PrivateChatInfo
userId={member.userId} userId={member.userId}
status={getRemovedBy(member)} status={getRemovedBy(member)}
forceShowSelf forceShowSelf
/> />
</ListItem> </ListItem>
))} ))}
{canDeleteMembers && ( </Island>
<FloatingActionButton )}
isShown
onClick={openRemoveUserModal}
ariaLabel={lang('Channel.EditAdmin.Permission.BanUsers')}
iconName="add-user-filled"
/>
)}
{chat && canDeleteMembers && (
<RemoveGroupUserModal
chat={chat}
isOpen={isRemoveUserModalOpen}
onClose={closeRemoveUserModal}
/>
)}
</div>
</div> </div>
{canDeleteMembers && (
<FloatingActionButton
isShown
onClick={openRemoveUserModal}
ariaLabel={lang('Channel.EditAdmin.Permission.BanUsers')}
iconName="add-user-filled"
/>
)}
{chat && canDeleteMembers && (
<RemoveGroupUserModal
chat={chat}
isOpen={isRemoveUserModalOpen}
onClose={closeRemoveUserModal}
/>
)}
</div> </div>
); );
}; };

View File

@ -22,6 +22,7 @@ import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import Avatar from '../../common/Avatar'; import Avatar from '../../common/Avatar';
import GroupChatInfo from '../../common/GroupChatInfo'; import GroupChatInfo from '../../common/GroupChatInfo';
import NothingFound from '../../common/NothingFound'; import NothingFound from '../../common/NothingFound';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -208,7 +209,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
function renderDiscussionGroups() { function renderDiscussionGroups() {
return ( return (
<div> <div>
<p className="section-help" dir="auto">{lang('DiscussionChannelHelp')}</p> <IslandDescription dir="auto">{lang('DiscussionChannelHelp')}</IslandDescription>
<div teactFastList> <div teactFastList>
<ListItem <ListItem
@ -239,7 +240,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
<NothingFound key="nothing-found" teactOrderKey={0} text="No discussion groups found" /> <NothingFound key="nothing-found" teactOrderKey={0} text="No discussion groups found" />
)} )}
</div> </div>
<p className="mt-4 mb-0 section-help" dir="auto">{lang('DiscussionChannelHelp2')}</p> <IslandDescription className="mt-4 mb-0" dir="auto">{lang('DiscussionChannelHelp2')}</IslandDescription>
<ConfirmDialog <ConfirmDialog
isOpen={isConfirmLinkGroupDialogOpen} isOpen={isConfirmLinkGroupDialogOpen}
onClose={closeConfirmLinkGroupDialog} onClose={closeConfirmLinkGroupDialog}
@ -255,7 +256,7 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section"> <Island>
<AnimatedIconWithPreview <AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.DiscussionGroups} tgsUrl={LOCAL_TGS_URLS.DiscussionGroups}
size={STICKER_SIZE_DISCUSSION_GROUPS} size={STICKER_SIZE_DISCUSSION_GROUPS}
@ -263,30 +264,32 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
/> />
{linkedChat && renderLinkedGroup()} {linkedChat && renderLinkedGroup()}
{!linkedChat && renderDiscussionGroups()} {!linkedChat && renderDiscussionGroups()}
</div> </Island>
{linkedChat && ( {linkedChat && (
<div className="section"> <>
<h3 className="section-heading">{lang('ChannelSettingsJoinTitle')}</h3> <IslandTitle>{lang('ChannelSettingsJoinTitle')}</IslandTitle>
<div className="ListItem narrow"> <Island>
<Checkbox
checked={isJoinToSend}
onCheck={handleJoinToSendCheck}
label={lang('ChannelSettingsJoinToSend')}
/>
</div>
{isJoinToSend && (
<div className="ListItem narrow"> <div className="ListItem narrow">
<Checkbox <Checkbox
checked={isJoinRequest} checked={isJoinToSend}
onCheck={handleJoinRequestCheck} onCheck={handleJoinToSendCheck}
label={lang('ChannelSettingsJoinRequest')} label={lang('ChannelSettingsJoinToSend')}
/> />
</div> </div>
)} {isJoinToSend && (
<p className="section-info section-info_push"> <div className="ListItem narrow">
<Checkbox
checked={isJoinRequest}
onCheck={handleJoinRequestCheck}
label={lang('ChannelSettingsJoinRequest')}
/>
</div>
)}
</Island>
<IslandDescription>
{isJoinToSend ? lang('ChannelSettingsJoinRequestInfo') : lang('ChannelSettingsJoinToSendInfo')} {isJoinToSend ? lang('ChannelSettingsJoinRequestInfo') : lang('ChannelSettingsJoinToSendInfo')}
</p> </IslandDescription>
</div> </>
)} )}
</div> </div>
</div> </div>

View File

@ -28,6 +28,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia'; import useMedia from '../../../hooks/useMedia';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island, { IslandDescription } from '../../gili/layout/Island';
import AvatarEditable from '../../ui/AvatarEditable'; import AvatarEditable from '../../ui/AvatarEditable';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
@ -320,13 +321,13 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section"> <AvatarEditable
<AvatarEditable isForForum={isForumEnabled}
isForForum={isForumEnabled} currentAvatarBlobUrl={currentAvatarBlobUrl}
currentAvatarBlobUrl={currentAvatarBlobUrl} onChange={handleSetPhoto}
onChange={handleSetPhoto} disabled={!canChangeInfo}
disabled={!canChangeInfo} />
/> <Island>
<div className="settings-edit"> <div className="settings-edit">
<InputText <InputText
id="group-title" id="group-title"
@ -431,11 +432,11 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
inactive inactive
/> />
</ListItem> </ListItem>
<div className="section-info section-info_push">{lang('ForumToggleDescription')}</div> <IslandDescription>{lang('ForumToggleDescription')}</IslandDescription>
</> </>
)} )}
</div> </Island>
<div className="section"> <Island>
<ListItem icon="group" multiline onClick={handleClickMembers}> <ListItem icon="group" multiline onClick={handleClickMembers}>
<span className="title">{lang('GroupMembers')}</span> <span className="title">{lang('GroupMembers')}</span>
<span className="subtitle">{formatInteger(chat.membersCount ?? 0)}</span> <span className="subtitle">{formatInteger(chat.membersCount ?? 0)}</span>
@ -455,12 +456,12 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
/> />
</div> </div>
)} )}
</div> </Island>
<div className="section"> <Island>
<ListItem icon="delete" ripple destructive onClick={openDeleteDialog}> <ListItem icon="delete" ripple destructive onClick={openDeleteDialog}>
{lang('DeleteMega')} {lang('DeleteMega')}
</ListItem> </ListItem>
</div> </Island>
</div> </div>
<FloatingActionButton <FloatingActionButton
isShown={isProfileFieldsTouched} isShown={isProfileFieldsTouched}

View File

@ -18,6 +18,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import PasswordConfirmModal from '../../common/PasswordConfirmModal'; import PasswordConfirmModal from '../../common/PasswordConfirmModal';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
@ -257,7 +258,7 @@ const ManageGroupAdminRights = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section"> <Island>
<ListItem inactive className="chat-item-clickable"> <ListItem inactive className="chat-item-clickable">
<PrivateChatInfo <PrivateChatInfo
userId={selectedChatMember.userId} userId={selectedChatMember.userId}
@ -266,8 +267,11 @@ const ManageGroupAdminRights = ({
/> />
</ListItem> </ListItem>
<h3 className="section-heading mt-4" dir="auto">{lang('EditAdminWhatCanDo')}</h3> </Island>
<IslandTitle dir="auto">{lang('EditAdminWhatCanDo')}</IslandTitle>
<Island>
<div className="ListItem"> <div className="ListItem">
<Checkbox <Checkbox
name="changeInfo" name="changeInfo"
@ -442,9 +446,9 @@ const ManageGroupAdminRights = ({
)} )}
{isFormFullyDisabled && ( {isFormFullyDisabled && (
<p className="section-info mb-4" dir="auto"> <IslandDescription className="mb-4" dir="auto">
{lang('EditAdminUnavailable')} {lang('EditAdminUnavailable')}
</p> </IslandDescription>
)} )}
{!isChannel && ( {!isChannel && (
@ -469,7 +473,7 @@ const ManageGroupAdminRights = ({
{lang('EditAdminRemoveAdmin')} {lang('EditAdminRemoveAdmin')}
</ListItem> </ListItem>
)} )}
</div> </Island>
</div> </div>
<FloatingActionButton <FloatingActionButton

View File

@ -21,11 +21,13 @@ import usePeerStoriesPolling from '../../../hooks/polling/usePeerStoriesPolling'
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation'; import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import NothingFound from '../../common/NothingFound'; import NothingFound from '../../common/NothingFound';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription } from '../../gili/layout/Island';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import InfiniteScroll from '../../ui/InfiniteScroll'; import InfiniteScroll from '../../ui/InfiniteScroll';
import InputText from '../../ui/InputText'; import InputText from '../../ui/InputText';
@ -86,7 +88,8 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
openChat, setUserSearchQuery, closeManagement, openChat, setUserSearchQuery, closeManagement,
toggleParticipantsHidden, setNewChatMembersDialogState, toggleManagement, toggleParticipantsHidden, setNewChatMembersDialogState, toggleManagement,
} = getActions(); } = getActions();
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
const inputRef = useRef<HTMLInputElement>(); const inputRef = useRef<HTMLInputElement>();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
@ -182,7 +185,7 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined { function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined {
return memberId === currentUserId || !canDeleteMembers ? undefined : [{ return memberId === currentUserId || !canDeleteMembers ? undefined : [{
title: lang('lng_context_remove_from_group'), title: oldLang('lng_context_remove_from_group'),
icon: 'stop', icon: 'stop',
handler: () => { handler: () => {
setDeletingUserId(memberId); setDeletingUserId(memberId);
@ -192,34 +195,37 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
function renderSearchField() { function renderSearchField() {
return ( return (
<div className="Management__filter" dir={lang.isRtl ? 'rtl' : undefined}> <Island dir={oldLang.isRtl ? 'rtl' : undefined}>
<InputText <InputText
ref={inputRef} ref={inputRef}
value={searchQuery} value={searchQuery}
onChange={handleFilterChange} onChange={handleFilterChange}
placeholder={lang('Search')} placeholder={oldLang('Search')}
noMargin
/> />
</div> </Island>
); );
} }
return ( return (
<div className="Management"> <div className="Management ManageGroupMembers">
{noAdmins && renderSearchField()} <div className="panel-content">
<div className="panel-content custom-scroll"> {noAdmins && renderSearchField()}
{canHideParticipants && !isChannel && ( {canHideParticipants && !isChannel && (
<div className="section"> <>
<ListItem icon="group" ripple onClick={handleToggleParticipantsHidden}> <Island>
<span>{lang('ChannelHideMembers')}</span> <ListItem icon="group" ripple onClick={handleToggleParticipantsHidden}>
<Switcher label={lang('ChannelHideMembers')} checked={areParticipantsHidden} /> <span>{oldLang('ChannelHideMembers')}</span>
</ListItem> <Switcher label={oldLang('ChannelHideMembers')} checked={areParticipantsHidden} />
<p className="section-info"> </ListItem>
{lang(areParticipantsHidden ? 'GroupMembers.MembersHiddenOn' : 'GroupMembers.MembersHiddenOff')} </Island>
</p> <IslandDescription>
</div> {oldLang(areParticipantsHidden ? 'GroupMembers.MembersHiddenOn' : 'GroupMembers.MembersHiddenOff')}
</IslandDescription>
</>
)} )}
<div className="section"> {viewportIds?.length ? (
{viewportIds?.length ? ( <Island className="island-list">
<InfiniteScroll <InfiniteScroll
className="picker-list custom-scroll" className="picker-list custom-scroll"
items={displayedIds} items={displayedIds}
@ -232,7 +238,6 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
<ListItem <ListItem
key={id} key={id}
className="chat-item-clickable scroll-item" className="chat-item-clickable scroll-item"
onClick={() => handleMemberClick(id)} onClick={() => handleMemberClick(id)}
contextActions={getMemberContextAction(id)} contextActions={getMemberContextAction(id)}
withPortalForMenu withPortalForMenu
@ -241,22 +246,22 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
</ListItem> </ListItem>
))} ))}
</InfiniteScroll> </InfiniteScroll>
) : !isSearching && viewportIds && !viewportIds.length ? ( </Island>
<NothingFound ) : !isSearching && viewportIds && !viewportIds.length ? (
teactOrderKey={0} <NothingFound
key="nothing-found" teactOrderKey={0}
text={isChannel ? 'No subscribers found' : 'No members found'} key="nothing-found"
/> text={lang(isChannel ? 'NoSubscribersFound' : 'NoMembersFound')}
) : ( />
<Loading /> ) : (
)} <Loading />
</div> )}
</div> </div>
{canAddMembers && ( {canAddMembers && (
<FloatingActionButton <FloatingActionButton
isShown isShown
onClick={handleNewMemberDialogOpen} onClick={handleNewMemberDialogOpen}
ariaLabel={lang('lng_channel_add_users')} ariaLabel={oldLang('lng_channel_add_users')}
iconName="add-user-filled" iconName="add-user-filled"
/> />
)} )}

View File

@ -27,6 +27,7 @@ import useManagePermissions from '../hooks/useManagePermissions';
import PaidMessagePrice from '../../common/paidMessage/PaidMessagePrice'; import PaidMessagePrice from '../../common/paidMessage/PaidMessagePrice';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import PermissionCheckboxList from '../../main/PermissionCheckboxList'; import PermissionCheckboxList from '../../main/PermissionCheckboxList';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -222,7 +223,6 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
const arePermissionsChanged = isPriceForMessagesChanged || havePermissionChanged; const arePermissionsChanged = isPriceForMessagesChanged || havePermissionChanged;
const arePermissionsLoading = progress === ManagementProgress.InProgress || isLoading; const arePermissionsLoading = progress === ManagementProgress.InProgress || isLoading;
return ( return (
<div <div
className="Management with-shifted-dropdown" className="Management with-shifted-dropdown"
@ -230,8 +230,8 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
+ `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT}px;`} + `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT}px;`}
> >
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section without-bottom-shadow"> <IslandTitle dir="auto">{lang('ChannelPermissionsHeader')}</IslandTitle>
<h3 className="section-heading" dir="auto">{lang('ChannelPermissionsHeader')}</h3> <Island className={buildClassName('without-bottom-shadow', isMediaDropdownOpen && 'dropdown-open')}>
<PermissionCheckboxList <PermissionCheckboxList
chatId={chat?.id} chatId={chat?.id}
isMediaDropdownOpen={isMediaDropdownOpen} isMediaDropdownOpen={isMediaDropdownOpen}
@ -243,17 +243,12 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
'DropdownList', 'DropdownList',
isMediaDropdownOpen && 'DropdownList--open', isMediaDropdownOpen && 'DropdownList--open',
)} )}
shiftedClassName={buildClassName('part', isMediaDropdownOpen && 'shifted')} shiftedClassName="part"
/> />
</div> </Island>
{arePaidMessagesAvailable && ( {arePaidMessagesAvailable && (
<div <Island>
className={buildClassName(
'section',
isMediaDropdownOpen && 'shifted',
)}
>
<ListItem onClick={handleChargeStarsForMessages}> <ListItem onClick={handleChargeStarsForMessages}>
<span>{lang('GroupMessagesChargePrice')}</span> <span>{lang('GroupMessagesChargePrice')}</span>
<Switcher <Switcher
@ -262,34 +257,26 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
checked={isPriceForMessagesOpen} checked={isPriceForMessagesOpen}
/> />
</ListItem> </ListItem>
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}> </Island>
{lang('RightsChargeStarsAbout')} )}
</p> {arePaidMessagesAvailable && (
</div> <IslandDescription dir={lang.isRtl ? 'rtl' : undefined}>
{lang('RightsChargeStarsAbout')}
</IslandDescription>
)} )}
{isPriceForMessagesOpen && ( {isPriceForMessagesOpen && (
<div <Island>
className={buildClassName(
'section',
isMediaDropdownOpen && 'shifted',
)}
>
<PaidMessagePrice <PaidMessagePrice
canChangeChargeForMessages canChangeChargeForMessages
isGroupChat isGroupChat
chargeForMessages={chargeForMessages} chargeForMessages={chargeForMessages}
onChange={handleChargeForMessagesChange} onChange={handleChargeForMessagesChange}
/> />
</div> </Island>
)} )}
<div <Island>
className={buildClassName(
'section',
isMediaDropdownOpen && 'shifted',
)}
>
<ListItem <ListItem
icon="delete-user" icon="delete-user"
multiline multiline
@ -299,16 +286,10 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
<span className="title">{lang('ChannelBlockedUsers')}</span> <span className="title">{lang('ChannelBlockedUsers')}</span>
<span className="subtitle">{removedUsersCount}</span> <span className="subtitle">{removedUsersCount}</span>
</ListItem> </ListItem>
</div> </Island>
<div
className={buildClassName(
'section',
isMediaDropdownOpen && 'shifted',
)}
>
<h3 className="section-heading" dir="auto">{lang('PrivacyExceptions')}</h3>
<IslandTitle dir="auto">{lang('PrivacyExceptions')}</IslandTitle>
<Island>
<ListItem <ListItem
icon="add-user" icon="add-user"
onClick={handleAddExceptionClick} onClick={handleAddExceptionClick}
@ -330,7 +311,7 @@ const ManageGroupPermissions: FC<OwnProps & StateProps> = ({
/> />
</ListItem> </ListItem>
))} ))}
</div> </Island>
</div> </div>
<FloatingActionButton <FloatingActionButton

View File

@ -17,6 +17,7 @@ import useOldLang from '../../../hooks/useOldLang';
import useManagePermissions from '../hooks/useManagePermissions'; import useManagePermissions from '../hooks/useManagePermissions';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandTitle } from '../../gili/layout/Island';
import PermissionCheckboxList from '../../main/PermissionCheckboxList'; import PermissionCheckboxList from '../../main/PermissionCheckboxList';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
@ -134,12 +135,12 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps> = ({
+ `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT + BEFORE_USER_INFO_HEIGHT}px;`} + `--before-shift-height: ${BEFORE_ITEMS_COUNT * ITEM_HEIGHT + BEFORE_USER_INFO_HEIGHT}px;`}
> >
<div className="custom-scroll"> <div className="custom-scroll">
<div className="section without-bottom-shadow"> <Island className={buildClassName('without-bottom-shadow', isMediaDropdownOpen && 'dropdown-open')}>
<ListItem inactive className="chat-item-clickable"> <ListItem inactive className="chat-item-clickable">
<PrivateChatInfo userId={selectedChatMember.userId} forceShowSelf /> <PrivateChatInfo userId={selectedChatMember.userId} forceShowSelf />
</ListItem> </ListItem>
<h3 className="section-heading mt-4" dir="auto">{oldLang('UserRestrictionsCanDo')}</h3> <IslandTitle className="mt-4" dir="auto">{oldLang('UserRestrictionsCanDo')}</IslandTitle>
<PermissionCheckboxList <PermissionCheckboxList
chatId={chat?.id} chatId={chat?.id}
isMediaDropdownOpen={isMediaDropdownOpen} isMediaDropdownOpen={isMediaDropdownOpen}
@ -151,22 +152,22 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps> = ({
isMediaDropdownOpen && 'DropdownList--open', isMediaDropdownOpen && 'DropdownList--open',
)} )}
dropdownClassName="DropdownListTrap" dropdownClassName="DropdownListTrap"
shiftedClassName={buildClassName('part', isMediaDropdownOpen && 'shifted')} shiftedClassName="part"
getControlIsDisabled={getControlIsDisabled} getControlIsDisabled={getControlIsDisabled}
/> />
</div> </Island>
{!isFormFullyDisabled && ( {!isFormFullyDisabled && (
<div <Island
className={buildClassName( className={buildClassName(
'section', 'part',
isMediaDropdownOpen && 'shifted', isMediaDropdownOpen && 'shifted',
)} )}
> >
<ListItem icon="delete-user" ripple destructive onClick={openBanConfirmationDialog}> <ListItem icon="delete-user" ripple destructive onClick={openBanConfirmationDialog}>
{oldLang('UserRestrictionsBlock')} {oldLang('UserRestrictionsBlock')}
</ListItem> </ListItem>
</div> </Island>
)} )}
</div> </div>

View File

@ -9,9 +9,11 @@ import { isChatChannel, sortUserIds } from '../../../global/helpers';
import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import { selectChat, selectChatFullInfo } from '../../../global/selectors';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang';
import NothingFound from '../../common/NothingFound'; import NothingFound from '../../common/NothingFound';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
type OwnProps = { type OwnProps = {
@ -39,6 +41,8 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
onClose, onClose,
isActive, isActive,
}) => { }) => {
const lang = useLang();
useHistoryBack({ useHistoryBack({
isActive, isActive,
onBack: onClose, onBack: onClose,
@ -64,9 +68,9 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="custom-scroll"> <div className="custom-scroll">
<div className="section" teactFastList> {memberIds?.length ? (
{memberIds ? ( <Island teactFastList>
memberIds.map((id, i) => ( {memberIds.map((id, i) => (
<ListItem <ListItem
key={id} key={id}
teactOrderKey={i} teactOrderKey={i}
@ -76,15 +80,13 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
> >
<PrivateChatInfo userId={id} forceShowSelf /> <PrivateChatInfo userId={id} forceShowSelf />
</ListItem> </ListItem>
)) ))}
) : ( </Island>
<NothingFound ) : (
teactOrderKey={0} <NothingFound
key="nothing-found" text={lang(isChannel ? 'NoSubscribersFound' : 'NoMembersFound')}
text={isChannel ? 'No subscribers found' : 'No members found'} />
/> )}
)}
</div>
</div> </div>
</div> </div>
); );

View File

@ -16,6 +16,7 @@ import useOldLang from '../../../hooks/useOldLang';
import useSyncEffect from '../../../hooks/useSyncEffect'; import useSyncEffect from '../../../hooks/useSyncEffect';
import CalendarModal from '../../common/CalendarModal'; import CalendarModal from '../../common/CalendarModal';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
@ -158,25 +159,25 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management ManageInvite"> <div className="Management ManageInvite">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section"> <Island>
<Checkbox <Checkbox
label={lang('ApproveNewMembers')} label={lang('ApproveNewMembers')}
subLabel={lang('ApproveNewMembersDescription')} subLabel={lang('ApproveNewMembersDescription')}
checked={isRequestNeeded} checked={isRequestNeeded}
onChange={handleIsRequestChange} onChange={handleIsRequestChange}
/> />
</div> </Island>
<div className="section"> <Island>
<InputText <InputText
className="link-name" className="link-name"
placeholder={lang('LinkNameHint')} placeholder={lang('LinkNameHint')}
value={title} value={title}
onChange={handleTitleChange} onChange={handleTitleChange}
/> />
<p className="section-help hint">{lang('LinkNameHelp')}</p> <IslandDescription>{lang('LinkNameHelp')}</IslandDescription>
</div> </Island>
<div className="section"> <IslandTitle>{lang('LimitByPeriod')}</IslandTitle>
<div className="section-heading">{lang('LimitByPeriod')}</div> <Island>
<RadioGroup <RadioGroup
name="expireOptions" name="expireOptions"
options={[ options={[
@ -211,39 +212,41 @@ const ManageInvite: FC<OwnProps & StateProps> = ({
{formatTime(lang, customExpireDate)} {formatTime(lang, customExpireDate)}
</Button> </Button>
)} )}
<p className="section-help hint">{lang('TimeLimitHelp')}</p> <IslandDescription>{lang('TimeLimitHelp')}</IslandDescription>
</div> </Island>
{!isRequestNeeded && ( {!isRequestNeeded && (
<div className="section"> <>
<div className="section-heading">{lang('LimitNumberOfUses')}</div> <IslandTitle>{lang('LimitNumberOfUses')}</IslandTitle>
<RadioGroup <Island>
name="usageOptions" <RadioGroup
options={[ name="usageOptions"
...DEFAULT_USAGE_LIMITS.map((n) => ({ value: n.toString(), label: n })), options={[
{ ...DEFAULT_USAGE_LIMITS.map((n) => ({ value: n.toString(), label: n })),
value: '0', {
label: lang('NoLimit'), value: '0',
}, label: lang('NoLimit'),
{ },
value: 'custom', {
label: lang('lng_group_invite_usage_custom'), value: 'custom',
}, label: lang('lng_group_invite_usage_custom'),
]} },
onChange={setSelectedUsageOption} ]}
selected={selectedUsageOption} onChange={setSelectedUsageOption}
/> selected={selectedUsageOption}
{selectedUsageOption === 'custom' && (
<input
className="form-control usage-limit"
type="number"
min="1"
max="99999"
value={customUsageLimit}
onChange={handleCustomUsageLimitChange}
/> />
)} {selectedUsageOption === 'custom' && (
<p className="section-help hint">{lang('UsesLimitHelp')}</p> <input
</div> className="form-control usage-limit"
type="number"
min="1"
max="99999"
value={customUsageLimit}
onChange={handleCustomUsageLimitChange}
/>
)}
<IslandDescription>{lang('UsesLimitHelp')}</IslandDescription>
</Island>
</>
)} )}
<FloatingActionButton <FloatingActionButton
isShown isShown

View File

@ -13,6 +13,7 @@ import useOldLang from '../../../hooks/useOldLang';
import LinkField from '../../common/LinkField'; import LinkField from '../../common/LinkField';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import Spinner from '../../ui/Spinner'; import Spinner from '../../ui/Spinner';
@ -70,11 +71,13 @@ const ManageInviteInfo = ({
if (!importers?.length && requesters?.length) return undefined; if (!importers?.length && requesters?.length) return undefined;
if (!importers) return <Spinner />; if (!importers) return <Spinner />;
return ( return (
<div className="section"> <>
<p className="section-heading">{importers.length ? lang('PeopleJoined', usage) : lang('NoOneJoined')}</p> <IslandTitle>{importers.length ? lang('PeopleJoined', usage) : lang('NoOneJoined')}</IslandTitle>
<p className="section-help"> <Island>
{!importers.length && ( {!importers.length && (
usageLimit ? lang('PeopleCanJoinViaLinkCount', usageLimit - usage) : lang('NoOneJoinedYet') <IslandDescription>
{usageLimit ? lang('PeopleCanJoinViaLinkCount', usageLimit - usage) : lang('NoOneJoinedYet')}
</IslandDescription>
)} )}
{importers.map((importer) => { {importers.map((importer) => {
const joinTime = formatMediaDateTime(lang, importer.date * 1000, true); const joinTime = formatMediaDateTime(lang, importer.date * 1000, true);
@ -93,8 +96,8 @@ const ManageInviteInfo = ({
</ListItem> </ListItem>
); );
})} })}
</p> </Island>
</div> </>
); );
}; };
@ -103,9 +106,9 @@ const ManageInviteInfo = ({
if (!requesters && importers) return <Spinner />; if (!requesters && importers) return <Spinner />;
if (!requesters?.length) return undefined; if (!requesters?.length) return undefined;
return ( return (
<div className="section"> <>
<p className="section-heading">{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</p> <IslandTitle>{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</IslandTitle>
<p className="section-help"> <Island>
{requesters.map((requester) => ( {requesters.map((requester) => (
<ListItem <ListItem
className="chat-item-clickable scroll-item small-icon" className="chat-item-clickable scroll-item small-icon"
@ -119,8 +122,8 @@ const ManageInviteInfo = ({
/> />
</ListItem> </ListItem>
))} ))}
</p> </Island>
</div> </>
); );
}; };
@ -128,35 +131,37 @@ const ManageInviteInfo = ({
<div className="Management ManageInviteInfo"> <div className="Management ManageInviteInfo">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
{!invite && ( {!invite && (
<p className="section-help">{lang('Loading')}</p> <IslandDescription>{lang('Loading')}</IslandDescription>
)} )}
{invite && ( {invite && (
<> <>
<div className="section"> <Island>
<LinkField title={invite.title} link={invite.link} className="invite-link" /> <LinkField title={invite.title} link={invite.link} className="invite-link" />
{Boolean(expireDate) && ( {Boolean(expireDate) && (
<p className="section-help"> <IslandDescription>
{isExpired {isExpired
? lang('ExpiredLink') ? lang('ExpiredLink')
: lang('LinkExpiresIn', `${formatFullDate(lang, expireDate)} ${formatTime(lang, expireDate)}`)} : lang('LinkExpiresIn', `${formatFullDate(lang, expireDate)} ${formatTime(lang, expireDate)}`)}
</p> </IslandDescription>
)} )}
</div> </Island>
{adminId && ( {adminId && (
<div className="section"> <>
<p className="section-heading">{lang('LinkCreatedeBy')}</p> <IslandTitle>{lang('LinkCreatedeBy')}</IslandTitle>
<ListItem <Island>
className="chat-item-clickable scroll-item small-icon" <ListItem
className="chat-item-clickable scroll-item small-icon"
onClick={() => openChat({ id: adminId })} onClick={() => openChat({ id: adminId })}
> >
<PrivateChatInfo <PrivateChatInfo
userId={adminId} userId={adminId}
status={formatMediaDateTime(lang, invite.date * 1000, true)} status={formatMediaDateTime(lang, invite.date * 1000, true)}
forceShowSelf forceShowSelf
/> />
</ListItem> </ListItem>
</div> </Island>
</>
)} )}
{renderImporters()} {renderImporters()}
{renderRequesters()} {renderRequesters()}

View File

@ -26,6 +26,7 @@ import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
import LinkField from '../../common/LinkField'; import LinkField from '../../common/LinkField';
import NothingFound from '../../common/NothingFound'; import NothingFound from '../../common/NothingFound';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import ListItem, { type MenuItemContextAction } from '../../ui/ListItem'; import ListItem, { type MenuItemContextAction } from '../../ui/ListItem';
@ -274,26 +275,33 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management ManageInvites"> <div className="Management ManageInvites">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
<div className="section"> <Island>
<AnimatedIconWithPreview <AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.Invite} tgsUrl={LOCAL_TGS_URLS.Invite}
size={STICKER_SIZE_INVITES} size={STICKER_SIZE_INVITES}
className="section-icon" className="section-icon"
/> />
<p className="section-help">{isChannel ? oldLang('PrimaryLinkHelpChannel') : oldLang('PrimaryLinkHelp')}</p> <IslandDescription>
</div> {isChannel ? oldLang('PrimaryLinkHelpChannel') : oldLang('PrimaryLinkHelp')}
</IslandDescription>
</Island>
{primaryInviteLink && ( {primaryInviteLink && (
<div className="section"> <>
<LinkField <IslandTitle>
className="settings-input" {chat?.usernames ? oldLang('PublicLink') : oldLang('lng_create_permanent_link_title')}
link={primaryInviteLink} </IslandTitle>
withShare <Island>
onRevoke={!chat?.usernames ? handlePrimaryRevoke : undefined} <LinkField
title={chat?.usernames ? oldLang('PublicLink') : oldLang('lng_create_permanent_link_title')} className="settings-input"
/> link={primaryInviteLink}
</div> noTitle
withShare
onRevoke={!chat?.usernames ? handlePrimaryRevoke : undefined}
/>
</Island>
</>
)} )}
<div className="section" teactFastList> <Island teactFastList>
<ListItem icon="add" withPrimaryColor key="create" className="create-item" onClick={handleCreateNewClick}> <ListItem icon="add" withPrimaryColor key="create" className="create-item" onClick={handleCreateNewClick}>
{oldLang('CreateNewLink')} {oldLang('CreateNewLink')}
</ListItem> </ListItem>
@ -314,11 +322,11 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
</span> </span>
</ListItem> </ListItem>
))} ))}
<p className="section-help hint" key="links-hint">{oldLang('ManageLinksInfoHelp')}</p> </Island>
</div> <IslandDescription>{oldLang('ManageLinksInfoHelp')}</IslandDescription>
{revokedExportedInvites && Boolean(revokedExportedInvites.length) && ( {revokedExportedInvites && Boolean(revokedExportedInvites.length) && (
<div className="section" teactFastList> <Island teactFastList>
<p className="section-help" key="title">{oldLang('RevokedLinks')}</p> <IslandDescription key="title">{oldLang('RevokedLinks')}</IslandDescription>
<ListItem <ListItem
icon="delete" icon="delete"
destructive destructive
@ -343,7 +351,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
</span> </span>
</ListItem> </ListItem>
))} ))}
</div> </Island>
)} )}
</div> </div>
<ConfirmDialog <ConfirmDialog

View File

@ -15,6 +15,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview'; import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import Spinner from '../../ui/Spinner'; import Spinner from '../../ui/Spinner';
@ -68,7 +69,7 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management ManageJoinRequests"> <div className="Management ManageJoinRequests">
<div className="custom-scroll"> <div className="custom-scroll">
<div className="section"> <Island>
<AnimatedIconWithPreview <AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.JoinRequest} tgsUrl={LOCAL_TGS_URLS.JoinRequest}
size={STICKER_SIZE_JOIN_REQUESTS} size={STICKER_SIZE_JOIN_REQUESTS}
@ -80,8 +81,8 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
<Button className="bulk-action-button" onClick={openRejectAllDialog} isText>Dismiss all</Button> <Button className="bulk-action-button" onClick={openRejectAllDialog} isText>Dismiss all</Button>
</div> </div>
)} )}
</div> </Island>
<div className="section" teactFastList> <Island teactFastList>
<p key="title"> <p key="title">
{!chat?.joinRequests ? lang('Loading') : chat.joinRequests.length {!chat?.joinRequests ? lang('Loading') : chat.joinRequests.length
? lang('JoinRequests', chat.joinRequests.length) : lang('NoMemberRequests')} ? lang('JoinRequests', chat.joinRequests.length) : lang('NoMemberRequests')}
@ -90,9 +91,9 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
<Spinner key="loading" /> <Spinner key="loading" />
)} )}
{chat?.joinRequests?.length === 0 && ( {chat?.joinRequests?.length === 0 && (
<p className="section-help" key="empty"> <IslandDescription key="empty">
{isChannel ? lang('NoSubscribeRequestsDescription') : lang('NoMemberRequestsDescription')} {isChannel ? lang('NoSubscribeRequestsDescription') : lang('NoMemberRequestsDescription')}
</p> </IslandDescription>
)} )}
{chat?.joinRequests?.map(({ userId, about, date }) => ( {chat?.joinRequests?.map(({ userId, about, date }) => (
<JoinRequest <JoinRequest
@ -104,7 +105,7 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
key={userId} key={userId}
/> />
))} ))}
</div> </Island>
</div> </div>
<ConfirmDialog <ConfirmDialog
isOpen={isAcceptAllDialogOpen} isOpen={isAcceptAllDialogOpen}

View File

@ -17,6 +17,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji'; import ReactionStaticEmoji from '../../common/reactions/ReactionStaticEmoji';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
@ -189,61 +190,65 @@ const ManageReactions: FC<OwnProps & StateProps> = ({
<div className="Management"> <div className="Management">
<div className="panel-content custom-scroll"> <div className="panel-content custom-scroll">
{Boolean(localReactionsLimit && shouldShowReactionsLimit) && ( {Boolean(localReactionsLimit && shouldShowReactionsLimit) && (
<div className="section"> <>
<h3 className="section-heading"> <IslandTitle>
{lang('MaximumReactionsHeader')} {lang('MaximumReactionsHeader')}
</h3> </IslandTitle>
<RangeSlider <Island>
min={1} <RangeSlider
max={maxUniqueReactions} min={1}
value={localReactionsLimit!} max={maxUniqueReactions}
onChange={handleReactionsLimitChange} value={localReactionsLimit!}
renderValue={renderReactionsMaxCountValue} onChange={handleReactionsLimitChange}
isCenteredLayout renderValue={renderReactionsMaxCountValue}
/> isCenteredLayout
<p className="section-info section-info_push"> />
</Island>
<IslandDescription>
{lang('ChannelReactions.MaxCount.Info')} {lang('ChannelReactions.MaxCount.Info')}
</p> </IslandDescription>
</div> </>
)} )}
<div className="section"> <IslandTitle>
<h3 className="section-heading"> {lang('AvailableReactions')}
{lang('AvailableReactions')} </IslandTitle>
</h3> <Island>
<RadioGroup <RadioGroup
selected={localEnabledReactions?.type || 'none'} selected={localEnabledReactions?.type || 'none'}
name="reactions" name="reactions"
options={reactionsOptions} options={reactionsOptions}
onChange={handleReactionsOptionChange} onChange={handleReactionsOptionChange}
/> />
<p className="section-info section-info_push"> </Island>
{localEnabledReactions?.type === 'all' && lang('EnableAllReactionsInfo')} <IslandDescription>
{localEnabledReactions?.type === 'some' && lang('EnableSomeReactionsInfo')} {localEnabledReactions?.type === 'all' && lang('EnableAllReactionsInfo')}
{!localEnabledReactions && lang('DisableReactionsInfo')} {localEnabledReactions?.type === 'some' && lang('EnableSomeReactionsInfo')}
</p> {!localEnabledReactions && lang('DisableReactionsInfo')}
</div> </IslandDescription>
{localEnabledReactions?.type === 'some' && ( {localEnabledReactions?.type === 'some' && (
<div className="section section-with-fab"> <>
<h3 className="section-heading"> <IslandTitle>
{lang('OnlyAllowThisReactions')} {lang('OnlyAllowThisReactions')}
</h3> </IslandTitle>
{availableActiveReactions?.map(({ reaction, title }) => ( <Island>
<div className="ListItem"> {availableActiveReactions?.map(({ reaction, title }) => (
<Checkbox <div key={reaction.emoticon} className="ListItem">
name={reaction.emoticon} <Checkbox
checked={localEnabledReactions?.allowed.some((r) => isSameReaction(reaction, r))} name={reaction.emoticon}
label={( checked={localEnabledReactions?.allowed.some((r) => isSameReaction(reaction, r))}
<div className="Reaction"> label={(
<ReactionStaticEmoji reaction={reaction} availableReactions={availableReactions} /> <div className="Reaction">
{title} <ReactionStaticEmoji reaction={reaction} availableReactions={availableReactions} />
</div> {title}
)} </div>
withIcon )}
onChange={handleReactionChange} withIcon
/> onChange={handleReactionChange}
</div> />
))} </div>
</div> ))}
</Island>
</>
)} )}
</div> </div>

View File

@ -28,6 +28,7 @@ import useOldLang from '../../../hooks/useOldLang';
import Avatar from '../../common/Avatar'; import Avatar from '../../common/Avatar';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import FloatingActionButton from '../../ui/FloatingActionButton'; import FloatingActionButton from '../../ui/FloatingActionButton';
@ -227,14 +228,14 @@ const ManageUser: FC<OwnProps & StateProps> = ({
return ( return (
<div className="Management"> <div className="Management">
<div className="custom-scroll"> <div className="custom-scroll">
<div className="section"> <PrivateChatInfo
<PrivateChatInfo userId={user.id}
userId={user.id} avatarSize="jumbo"
avatarSize="jumbo" noStatusOrTyping
noStatusOrTyping noEmojiStatus
noEmojiStatus withFullInfo
withFullInfo />
/> <Island>
<div className="settings-edit"> <div className="settings-edit">
<InputText <InputText
ref={firstNameRef} ref={firstNameRef}
@ -262,7 +263,9 @@ const ManageUser: FC<OwnProps & StateProps> = ({
noReplaceNewlines noReplaceNewlines
/> />
</div> </div>
<p className="section-edit-info" dir="auto">{lang('EditUserNoteHint')}</p> </Island>
<IslandDescription dir="auto">{lang('EditUserNoteHint')}</IslandDescription>
<Island>
<div className="ListItem narrow"> <div className="ListItem narrow">
<Checkbox <Checkbox
checked={isNotificationsEnabled} checked={isNotificationsEnabled}
@ -273,40 +276,42 @@ const ManageUser: FC<OwnProps & StateProps> = ({
onChange={handleNotificationChange} onChange={handleNotificationChange}
/> />
</div> </div>
</div> </Island>
{canSetPersonalPhoto && ( {canSetPersonalPhoto && (
<div className="section"> <>
<ListItem icon="camera-add" ripple onClick={handleSuggestPhoto}> <Island>
<span className="list-item-ellipsis">{oldLang('UserInfo.SuggestPhoto', user.firstName)}</span> <ListItem icon="camera-add" ripple onClick={handleSuggestPhoto}>
</ListItem> <span className="list-item-ellipsis">{oldLang('UserInfo.SuggestPhoto', user.firstName)}</span>
<ListItem icon="camera-add" ripple onClick={handleSetPersonalPhoto}>
<span className="list-item-ellipsis">{oldLang('UserInfo.SetCustomPhoto', user.firstName)}</span>
</ListItem>
{personalPhoto && (
<ListItem
leftElement={(
<Avatar
photo={notPersonalPhoto}
noPersonalPhoto
peer={user}
size="mini"
className="personal-photo"
/>
)}
ripple
onClick={openResetPersonalPhotoDialog}
>
{oldLang('UserInfo.ResetCustomPhoto')}
</ListItem> </ListItem>
)} <ListItem icon="camera-add" ripple onClick={handleSetPersonalPhoto}>
<p className="section-help" dir="auto">{oldLang('UserInfo.CustomPhotoInfo', user.firstName)}</p> <span className="list-item-ellipsis">{oldLang('UserInfo.SetCustomPhoto', user.firstName)}</span>
</div> </ListItem>
{personalPhoto && (
<ListItem
leftElement={(
<Avatar
photo={notPersonalPhoto}
noPersonalPhoto
peer={user}
size="mini"
className="personal-photo"
/>
)}
ripple
onClick={openResetPersonalPhotoDialog}
>
{oldLang('UserInfo.ResetCustomPhoto')}
</ListItem>
)}
</Island>
<IslandDescription dir="auto">{oldLang('UserInfo.CustomPhotoInfo', user.firstName)}</IslandDescription>
</>
)} )}
<div className="section"> <Island>
<ListItem icon="delete" ripple destructive onClick={openDeleteDialog}> <ListItem icon="delete" ripple destructive onClick={openDeleteDialog}>
{oldLang('DeleteContact')} {oldLang('DeleteContact')}
</ListItem> </ListItem>
</div> </Island>
</div> </div>
<FloatingActionButton <FloatingActionButton
isShown={isProfileFieldsTouched} isShown={isProfileFieldsTouched}

View File

@ -3,10 +3,17 @@
.Management { .Management {
height: 100%; height: 100%;
& > .custom-scroll { & > .custom-scroll,
& > .panel-content {
overflow-x: hidden; overflow-x: hidden;
overflow-y: scroll; overflow-y: scroll;
height: 100%; height: 100%;
padding: 1rem;
background-color: var(--color-background-secondary);
@include mixins.adapt-padding-to-scrollbar(1rem);
} }
.personal-photo { .personal-photo {
@ -14,116 +21,58 @@
margin-right: 2rem; margin-right: 2rem;
} }
.section { .section-icon {
padding: 1rem 0.5rem; margin: 0 auto 2rem;
}
@include mixins.adapt-padding-to-scrollbar(0.5rem); & > .custom-scroll > .ChatInfo,
@include mixins.side-panel-section; & > .panel-content > .ChatInfo {
display: flex;
flex-direction: column;
align-items: center;
&.wide { margin-bottom: 1rem;
padding: 1.5rem; padding: 1rem 1.5rem;
text-align: center;
}
.ChatInfo .title h3 {
margin-bottom: 0;
}
.ListItem {
.Reaction {
display: flex;
align-items: center;
} }
&:first-of-type { .ReactionStaticEmoji {
padding-top: 1rem; width: 1.5rem;
margin-right: 1.6875rem;
} }
&.no-border { &.with-checkbox {
border-top: none; body.is-ios &::after,
} body.is-android &::after {
bottom: -1rem;
&.section-with-fab {
padding-bottom: 3.5rem;
}
> .ChatInfo {
margin: 0 0 2rem !important;
.title h3 {
margin-bottom: 0;
} }
} }
.section-icon { &.exceptions-member {
margin: 0 auto 2rem; .ChatInfo .status {
} white-space: pre-wrap;
.ListItem {
.Reaction {
display: flex;
align-items: center;
}
.ReactionStaticEmoji {
width: 1.5rem;
margin-right: 1.6875rem;
}
&.with-checkbox {
body.is-ios &::after,
body.is-android &::after {
bottom: -1rem;
}
}
&.exceptions-member {
.ChatInfo .status {
white-space: pre-wrap;
}
} }
} }
}
.section-heading { .invite-link {
position: relative; padding: 0 1rem;
}
padding-inline-start: 1rem; .input-admin-title {
margin-top: 0.875rem;
font-size: 1rem; margin-inline: 1rem;
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
&[dir="auto"] {
text-align: initial;
}
}
.section-help {
padding: 0 1rem;
line-height: 1.375rem;
color: var(--color-text-secondary);
&[dir="auto"] {
text-align: initial;
}
}
.section-edit-info,
.section-info {
padding: 0 1rem;
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.section-edit-info {
margin-top: -0.875rem;
}
.invite-link {
padding: 0 1rem;
}
.section-info_push {
margin-top: 0.25rem;
}
.input-admin-title {
margin-top: 0.875rem;
margin-inline: 1rem;
}
&[dir="rtl"] {
text-align: right;
}
} }
textarea.form-control { textarea.form-control {
@ -183,8 +132,27 @@
} }
} }
.ManageGroupMembers { .ManageGroupMembers > .panel-content {
padding: 0.5rem 1rem; overflow: hidden;
display: flex;
flex-direction: column;
.island-list {
overflow: hidden;
display: flex;
flex: 0 1 auto;
flex-direction: column;
min-height: 0;
padding-block: 0;
.picker-list {
overflow-x: hidden;
overflow-y: auto;
flex-grow: 1;
padding-block: 0.5rem;
}
}
} }
.ManageInvites { .ManageInvites {
@ -311,6 +279,9 @@
width: 100%; width: 100%;
height: 0; height: 0;
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
transition: height 0.25s ease-in-out;
&::before { &::before {
content: ""; content: "";
@ -333,27 +304,28 @@
} }
.without-bottom-shadow { .without-bottom-shadow {
padding-bottom: 0; --before-shift-height: 48px;
position: relative;
overflow: hidden;
padding: 0.5rem 0.5rem 0;
box-shadow: none; box-shadow: none;
&.dropdown-open .DropdownListTrap {
height: var(--shift-height);
}
} }
.part { .part {
margin: 0 -1.5rem; padding-bottom: 0.5rem;
padding: 0 1.5rem 1rem;
@include mixins.side-panel-section;
} }
.section, .part { .section, .part {
position: relative; position: relative;
transition: 0.25s ease-in-out transform;
&.shifted {
transform: translateY(var(--shift-height));
}
} }
} }
.settings-edit { .settings-edit {
padding: 0 1rem !important; padding: 1.125rem !important;
padding-bottom: 0 !important;
} }

View File

@ -3,6 +3,8 @@
.root { .root {
overflow-x: hidden; overflow-x: hidden;
overflow-y: scroll; overflow-y: scroll;
padding: 1rem;
background-color: var(--color-background-secondary);
} }
.noResults { .noResults {
@ -19,9 +21,6 @@
.section { .section {
padding: 1.5rem; padding: 1.5rem;
padding-inline-end: calc(1.5rem - var(--scrollbar-width));
@include mixins.side-panel-section;
} }
.user :global(.status) { .user :global(.status) {
@ -113,10 +112,6 @@
flex-direction: column-reverse; flex-direction: column-reverse;
} }
.giveawayButton {
margin: 0 -1rem 0.5rem;
}
.giveawayIcon { .giveawayIcon {
width: 2.75rem; width: 2.75rem;
height: 2.75rem; height: 2.75rem;
@ -134,7 +129,3 @@
right: 0.5rem; right: 0.5rem;
transform: translate(0, -50%); transform: translate(0, -50%);
} }
.boostInfo {
margin: 0 -1rem;
}

View File

@ -22,6 +22,7 @@ import Icon from '../../common/icons/Icon';
import LinkField from '../../common/LinkField'; import LinkField from '../../common/LinkField';
import PremiumProgress from '../../common/PremiumProgress'; import PremiumProgress from '../../common/PremiumProgress';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import Island, { IslandDescription, IslandTitle } from '../../gili/layout/Island';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import Spinner from '../../ui/Spinner'; import Spinner from '../../ui/Spinner';
@ -222,7 +223,7 @@ const BoostStatistics = ({
return ( return (
<ListItem <ListItem
className={buildClassName(styles.boostInfo, 'chat-item-clickable')} className="chat-item-clickable"
onClick={() => handleBoosterClick(boost.userId)} onClick={() => handleBoosterClick(boost.userId)}
> >
<PrivateChatInfo <PrivateChatInfo
@ -254,6 +255,25 @@ const BoostStatistics = ({
openGiveawayModal({ chatId, prepaidGiveaway }); openGiveawayModal({ chatId, prepaidGiveaway });
}); });
function renderLoadMore() {
if (!boostersToLoadCount) return undefined;
return (
<ListItem
key="load-more"
className={styles.showMore}
disabled={boostStatistics?.isLoadingBoosters}
onClick={handleLoadMore}
>
{boostStatistics?.isLoadingBoosters ? (
<Spinner className={styles.loadMoreSpinner} />
) : (
<Icon name="down" className={styles.down} />
)}
{lang('ShowVotes', boostersToLoadCount, 'i')}
</ListItem>
);
}
function renderContent() { function renderContent() {
let listToRender; let listToRender;
if (tabType === 'boostList') { if (tabType === 'boostList') {
@ -267,9 +287,10 @@ const BoostStatistics = ({
} }
return ( return (
<div className={styles.section}> <Island>
{listToRender?.map((boost) => renderBoostList(boost))} {listToRender?.map((boost) => renderBoostList(boost))}
</div> {renderLoadMore()}
</Island>
); );
} }
@ -278,7 +299,7 @@ const BoostStatistics = ({
{!isLoaded && <Loading />} {!isLoaded && <Loading />}
{isLoaded && statsOverview && ( {isLoaded && statsOverview && (
<> <>
<div className={styles.section}> <Island>
<PremiumProgress <PremiumProgress
leftText={lang('BoostsLevel', currentLevel)} leftText={lang('BoostsLevel', currentLevel)}
rightText={hasNextLevel ? lang('BoostsLevel', currentLevel + 1) : undefined} rightText={hasNextLevel ? lang('BoostsLevel', currentLevel + 1) : undefined}
@ -287,73 +308,75 @@ const BoostStatistics = ({
floatingBadgeIcon="boost" floatingBadgeIcon="boost"
/> />
<StatisticsOverview className={styles.stats} statistics={statsOverview} type="boost" /> <StatisticsOverview className={styles.stats} statistics={statsOverview} type="boost" />
</div> </Island>
{statsOverview.prepaidGiveaways && ( {statsOverview.prepaidGiveaways && (
<div className={styles.section}> <>
<h4 className={styles.sectionHeader} dir={lang.isRtl ? 'rtl' : undefined}> <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
{lang('BoostingPreparedGiveaways')} {lang('BoostingPreparedGiveaways')}
</h4> </IslandTitle>
{statsOverview?.prepaidGiveaways?.map((prepaidGiveaway) => { <Island>
const isStarsGiveaway = 'stars' in prepaidGiveaway; {statsOverview?.prepaidGiveaways?.map((prepaidGiveaway) => {
const isStarsGiveaway = 'stars' in prepaidGiveaway;
return ( return (
<ListItem <ListItem
key={prepaidGiveaway.id} key={prepaidGiveaway.id}
className="chat-item-clickable" className="chat-item-clickable"
onClick={() => launchPrepaidGiveawayHandler(prepaidGiveaway)} onClick={() => launchPrepaidGiveawayHandler(prepaidGiveaway)}
> >
<div className={buildClassName(styles.status, 'status-clickable')}> <div className={buildClassName(styles.status, 'status-clickable')}>
<div> <div>
{isStarsGiveaway
? (
<img
src={GiftStar}
className={styles.giveawayIcon}
alt={lang('GiftStar')}
/>
) : (
<img
src={GIVEAWAY_IMG_LIST[prepaidGiveaway.months] || GIVEAWAY_IMG_LIST[3]}
className={styles.giveawayIcon}
alt={lang('Giveaway')}
/>
)}
</div>
<div className={styles.info}>
<h3>
{isStarsGiveaway {isStarsGiveaway
? lang('Giveaway.Stars.Prepaid.Title', prepaidGiveaway.stars) ? (
: lang('BoostingTelegramPremiumCountPlural', prepaidGiveaway.quantity)} <img
</h3> src={GiftStar}
<p className={styles.month}> className={styles.giveawayIcon}
{ alt={lang('GiftStar')}
isStarsGiveaway ? lang('Giveaway.Stars.Prepaid.Desc', prepaidGiveaway.quantity) />
: lang('PrepaidGiveawayMonths', prepaidGiveaway.months) ) : (
} <img
</p> src={GIVEAWAY_IMG_LIST[prepaidGiveaway.months] || GIVEAWAY_IMG_LIST[3]}
</div> className={styles.giveawayIcon}
<div className={styles.quantity}> alt={lang('Giveaway')}
<div className={buildClassName(styles.floatingBadge, />
styles.floatingBadgeButtonColor, )}
styles.floatingBadgeButton)} </div>
> <div className={styles.info}>
<Icon name="boost" className={styles.floatingBadgeIcon} /> <h3>
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}> {isStarsGiveaway
{isStarsGiveaway ? prepaidGiveaway.boosts ? lang('Giveaway.Stars.Prepaid.Title', prepaidGiveaway.stars)
: prepaidGiveaway.quantity * (giveawayBoostsPerPremium ?? GIVEAWAY_BOOST_PER_PREMIUM)} : lang('BoostingTelegramPremiumCountPlural', prepaidGiveaway.quantity)}
</h3>
<p className={styles.month}>
{
isStarsGiveaway ? lang('Giveaway.Stars.Prepaid.Desc', prepaidGiveaway.quantity)
: lang('PrepaidGiveawayMonths', prepaidGiveaway.months)
}
</p>
</div>
<div className={styles.quantity}>
<div className={buildClassName(styles.floatingBadge,
styles.floatingBadgeButtonColor,
styles.floatingBadgeButton)}
>
<Icon name="boost" className={styles.floatingBadgeIcon} />
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
{isStarsGiveaway ? prepaidGiveaway.boosts
: prepaidGiveaway.quantity * (giveawayBoostsPerPremium ?? GIVEAWAY_BOOST_PER_PREMIUM)}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </ListItem>
</ListItem> );
); })}
})} </Island>
<p className="text-muted hint" key="links-hint">{lang('BoostingSelectPaidGiveaway')}</p> <IslandDescription>{lang('BoostingSelectPaidGiveaway')}</IslandDescription>
</div> </>
)} )}
<div className={styles.section}> {shouldDisplayGiftList ? (
{shouldDisplayGiftList ? ( <Island>
<div <div
className={buildClassName(styles.boostSection, styles.content)} className={buildClassName(styles.boostSection, styles.content)}
> >
@ -368,52 +391,45 @@ const BoostStatistics = ({
</Transition> </Transition>
<SquareTabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={setActiveTab} /> <SquareTabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={setActiveTab} />
</div> </div>
) : ( </Island>
<> ) : (
<h4 className={styles.sectionHeader} dir={lang.isRtl ? 'rtl' : undefined}> <>
{lang('BoostingBoostsCount', boostStatistics?.boosts?.count)} <IslandTitle dir={lang.isRtl ? 'rtl' : undefined}>
</h4> {lang('BoostingBoostsCount', boostStatistics?.boosts?.count)}
{!boostStatistics?.boosts?.list?.length && ( </IslandTitle>
<div className={styles.noResults}> {!boostStatistics?.boosts?.list?.length ? (
{lang(isChannel ? 'NoBoostersHint' : 'NoBoostersGroupHint')} <IslandDescription>
</div> {lang(isChannel ? 'NoBoostersHint' : 'NoBoostersGroupHint')}
)} </IslandDescription>
{boostStatistics?.boosts?.list?.map((boost) => renderBoostList(boost))} ) : (
</> <Island>
)} {boostStatistics?.boosts?.list?.map((boost) => renderBoostList(boost))}
{Boolean(boostersToLoadCount) && ( {renderLoadMore()}
<ListItem </Island>
key="load-more" )}
className={styles.showMore} </>
disabled={boostStatistics?.isLoadingBoosters} )}
onClick={handleLoadMore} <IslandTitle>{lang('LinkForBoosting')}</IslandTitle>
> <Island>
{boostStatistics?.isLoadingBoosters ? ( <LinkField link={status!.boostUrl} withShare noTitle />
<Spinner className={styles.loadMoreSpinner} /> </Island>
) : (
<Icon name="down" className={styles.down} />
)}
{lang('ShowVotes', boostersToLoadCount, 'i')}
</ListItem>
)}
</div>
<LinkField className={styles.section} link={status!.boostUrl} withShare title={lang('LinkForBoosting')} />
{isGiveawayAvailable && ( {isGiveawayAvailable && (
<div className={styles.section}> <>
<ListItem <Island>
key="load-more" <ListItem
icon="gift" key="load-more"
onClick={handleGiveawayClick} icon="gift"
className={styles.giveawayButton} onClick={handleGiveawayClick}
> >
{lang('BoostingGetBoostsViaGifts')} {lang('BoostingGetBoostsViaGifts')}
</ListItem> </ListItem>
<p className="text-muted hint" key="links-hint"> </Island>
<IslandDescription>
{lang( {lang(
isChannel ? 'BoostingGetMoreBoosts' : 'BoostingGetMoreBoostsGroup', isChannel ? 'BoostingGetMoreBoosts' : 'BoostingGetMoreBoostsGroup',
)} )}
</p> </IslandDescription>
</div> </>
)} )}
</> </>
)} )}

View File

@ -15,9 +15,11 @@ import { callApi } from '../../../api/gramjs';
import { isGraph } from './helpers/isGraph'; import { isGraph } from './helpers/isGraph';
import useForceUpdate from '../../../hooks/useForceUpdate'; import useForceUpdate from '../../../hooks/useForceUpdate';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island, { IslandTitle } from '../../gili/layout/Island';
import InfiniteScroll from '../../ui/InfiniteScroll'; import InfiniteScroll from '../../ui/InfiniteScroll';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import StatisticsMessagePublicForward from './StatisticsMessagePublicForward'; import StatisticsMessagePublicForward from './StatisticsMessagePublicForward';
@ -62,7 +64,8 @@ function MessageStatistics({
dcId, dcId,
messageId, messageId,
}: OwnProps & StateProps) { }: OwnProps & StateProps) {
const lang = useOldLang(); const lang = useLang();
const oldLang = useOldLang();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const loadedChartsRef = useRef<Set<string>>(new Set()); const loadedChartsRef = useRef<Set<string>>(new Set());
@ -141,10 +144,10 @@ function MessageStatistics({
LovelyChart.create( LovelyChart.create(
containerRef.current!.children[index] as HTMLElement, containerRef.current!.children[index] as HTMLElement,
{ {
title: lang((GRAPH_TITLES as Record<string, string>)[name]), title: oldLang((GRAPH_TITLES as Record<string, string>)[name]),
...zoomToken ? { ...zoomToken ? {
onZoom: (x: number) => callApi('fetchStatisticsAsyncGraph', { token: zoomToken, x, dcId }), onZoom: (x: number) => callApi('fetchStatisticsAsyncGraph', { token: zoomToken, x, dcId }),
zoomOutLabel: lang('Graph.ZoomOut'), zoomOutLabel: oldLang('Graph.ZoomOut'),
} : {}, } : {},
...graph, ...graph,
}, },
@ -156,7 +159,7 @@ function MessageStatistics({
forceUpdate(); forceUpdate();
})(); })();
}, [ }, [
isReady, statistics, lang, chatId, messageId, loadStatisticsAsyncGraph, dcId, forceUpdate, isReady, statistics, oldLang, chatId, messageId, loadStatisticsAsyncGraph, dcId, forceUpdate,
]); ]);
const handleLoadMore = useLastCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useLastCallback(({ direction }: { direction: LoadMoreDirection }) => {
@ -174,11 +177,14 @@ function MessageStatistics({
key={`${chatId}-${messageId}`} key={`${chatId}-${messageId}`}
className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)} className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}
> >
<StatisticsOverview statistics={statistics} type="message" title={lang('StatisticOverview')} /> <IslandTitle>{lang('StatisticOverview')}</IslandTitle>
<Island>
<StatisticsOverview statistics={statistics} type="message" />
</Island>
{(!loadedChartsRef.current.size || !statistics.publicForwardsData) && <Loading />} {(!loadedChartsRef.current.size || !statistics.publicForwardsData) && <Loading />}
<div ref={containerRef} data-stricterdom-ignore> <div ref={containerRef} className={styles.graphContainer} data-stricterdom-ignore>
{GRAPHS.map((graph) => { {GRAPHS.map((graph) => {
const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph); const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph);
return ( return (
@ -189,18 +195,19 @@ function MessageStatistics({
{Boolean(statistics.publicForwards) && ( {Boolean(statistics.publicForwards) && (
<div className={styles.publicForwards}> <div className={styles.publicForwards}>
<h2 className={styles.publicForwardsTitle}>{lang('Stats.Message.PublicShares')}</h2> <IslandTitle>{lang('StatsMessagePublicShares')}</IslandTitle>
<Island>
<InfiniteScroll <InfiniteScroll
items={statistics.publicForwardsData} items={statistics.publicForwardsData}
itemSelector=".statistic-public-forward" itemSelector=".statistic-public-forward"
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
noFastList noFastList
> >
{(statistics.publicForwardsData as ApiMessagePublicForward[]).map((item) => ( {(statistics.publicForwardsData as ApiMessagePublicForward[]).map((item) => (
<StatisticsMessagePublicForward key={item.messageId} data={item} /> <StatisticsMessagePublicForward key={item.messageId} data={item} />
))} ))}
</InfiniteScroll> </InfiniteScroll>
</Island>
</div> </div>
)} )}
</div> </div>

View File

@ -4,17 +4,16 @@
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden; overflow-y: hidden;
height: 100%; height: 100%;
padding: 1rem;
} }
.graph { .graph {
margin-bottom: 1rem; margin-bottom: 1rem;
border-bottom: 0.0625rem solid var(--color-borders);
opacity: 1; opacity: 1;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
&:last-of-type { &:last-of-type {
margin-bottom: 0; margin-bottom: 0;
border-bottom: none;
} }
&.hidden { &.hidden {
@ -23,21 +22,25 @@
} }
} }
.charts {
display: flex;
flex-direction: column;
}
.ready { .ready {
overflow-y: scroll !important; overflow-y: scroll !important;
} }
.section { .balanceSection {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
padding: 1rem;
padding: 1rem 0.75rem;
border-bottom: 0.0625rem solid var(--color-borders);
} }
.topText { .balanceTitle {
display: block; font-size: 0.9375rem;
font-weight: var(--font-weight-semibold);
} }
.availableReward { .availableReward {

View File

@ -20,6 +20,7 @@ import useOldLang from '../../../hooks/useOldLang';
import AboutMonetizationModal from '../../common/AboutMonetizationModal.async'; import AboutMonetizationModal from '../../common/AboutMonetizationModal.async';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
import SafeLink from '../../common/SafeLink'; import SafeLink from '../../common/SafeLink';
import Island, { IslandDescription } from '../../gili/layout/Island';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import ConfirmDialog from '../../ui/ConfirmDialog'; import ConfirmDialog from '../../ui/ConfirmDialog';
import Link from '../../ui/Link'; import Link from '../../ui/Link';
@ -230,29 +231,31 @@ const MonetizationStatistics = ({
} }
return ( return (
<div className={buildClassName(styles.root, 'custom-scroll', isReady && styles.ready)}> <div className={buildClassName(styles.root, 'panel-content custom-scroll', isReady && styles.ready)}>
<div className={buildClassName(styles.section, styles.topText)}>{topText}</div> <IslandDescription>{topText}</IslandDescription>
<StatisticsOverview <Island>
statistics={statistics} <StatisticsOverview
isToncoin statistics={statistics}
type="monetization" isToncoin
title={oldLang('MonetizationOverview')} type="monetization"
subtitle={ title={oldLang('MonetizationOverview')}
<div className={styles.textBottom}>{oldLang('MonetizationProceedsTONInfo')}</div> />
} </Island>
/> <IslandDescription>{oldLang('MonetizationProceedsTONInfo')}</IslandDescription>
{!loadedChartsRef.current.size && <Loading />} {!loadedChartsRef.current.size && <Loading />}
<div ref={containerRef} className={styles.section} data-stricterdom-ignore> <Island>
{MONETIZATION_GRAPHS.filter(Boolean).map((graph) => ( <div ref={containerRef} data-stricterdom-ignore className={styles.charts}>
<div key={graph} className={buildClassName(styles.graph, styles.hidden)} /> {MONETIZATION_GRAPHS.filter(Boolean).map((graph) => (
))} <div key={graph} className={buildClassName(styles.graph, styles.hidden)} />
</div> ))}
</div>
</Island>
<div className={styles.section}> <Island className={styles.balanceSection}>
{oldLang('lng_channel_earn_balance_title')} <h4 className={styles.balanceTitle}>{oldLang('lng_channel_earn_balance_title')}</h4>
{renderAvailableReward()} {renderAvailableReward()}
@ -264,8 +267,8 @@ const MonetizationStatistics = ({
{oldLang('MonetizationWithdraw')} {oldLang('MonetizationWithdraw')}
</Button> </Button>
<div className={styles.textBottom}>{rewardsText}</div> </Island>
</div> <IslandDescription>{rewardsText}</IslandDescription>
<AboutMonetizationModal <AboutMonetizationModal
isOpen={isAboutMonetizationModalOpen} isOpen={isAboutMonetizationModalOpen}

View File

@ -3,9 +3,9 @@
overflow-y: hidden; overflow-y: hidden;
height: 100%; height: 100%;
border-top: 1px solid transparent; padding: 1rem;
transition: border-top-color 0.2s ease-in-out; background-color: var(--color-background-secondary);
:global(.lovely-chart--container) { :global(.lovely-chart--container) {
font: inherit !important; font: inherit !important;
@ -13,7 +13,7 @@
} }
:global(.lovely-chart--header) { :global(.lovely-chart--header) {
margin: 0 0.75rem; margin: 0.75rem;
} }
:global(.lovely-chart--header), :global(.lovely-chart--header),
@ -35,7 +35,8 @@
} }
.messages, .publicForwards { .messages, .publicForwards {
padding: 1rem 0; margin-top: 1rem;
padding: 0;
&-title { &-title {
padding-left: 0.75rem; padding-left: 0.75rem;
@ -55,20 +56,25 @@
overflow-y: scroll !important; overflow-y: scroll !important;
} }
.graphContainer {
margin-top: 1rem;
}
.graph { .graph {
margin-bottom: 1rem; border-radius: var(--border-radius-island);
border-bottom: 1px solid var(--color-borders);
opacity: 1; opacity: 1;
background-color: var(--color-background);
box-shadow: 0 1px 4px 0 #0000000D;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
&:last-of-type { & + & {
margin-bottom: 0; margin-top: 1rem;
border-bottom: none;
} }
&.hidden { &.hidden {
margin: 0; margin: 0;
border-bottom: none;
opacity: 0; opacity: 0;
} }
} }

View File

@ -26,6 +26,7 @@ import { isGraph } from './helpers/isGraph';
import useForceUpdate from '../../../hooks/useForceUpdate'; import useForceUpdate from '../../../hooks/useForceUpdate';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Island from '../../gili/layout/Island';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import StatisticsOverview from './StatisticsOverview'; import StatisticsOverview from './StatisticsOverview';
import StatisticsRecentMessage from './StatisticsRecentMessage'; import StatisticsRecentMessage from './StatisticsRecentMessage';
@ -200,16 +201,18 @@ const Statistics = ({
return ( return (
<div className={buildClassName(styles.root, 'panel-content custom-scroll', isReady && styles.ready)}> <div className={buildClassName(styles.root, 'panel-content custom-scroll', isReady && styles.ready)}>
{statistics && ( {statistics && (
<StatisticsOverview <Island>
statistics={statistics} <StatisticsOverview
type={isGroup ? 'group' : 'channel'} statistics={statistics}
title={lang('StatisticOverview')} type={isGroup ? 'group' : 'channel'}
/> title={lang('StatisticOverview')}
/>
</Island>
)} )}
{!loadedChartsRef.current.size && <Loading />} {!loadedChartsRef.current.size && <Loading />}
<div ref={containerRef} data-stricterdom-ignore> <div ref={containerRef} data-stricterdom-ignore className={styles.graphContainer}>
{graphs.map((graph) => { {graphs.map((graph) => {
const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph); const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph);
return ( return (
@ -219,7 +222,7 @@ const Statistics = ({
</div> </div>
{Boolean((statistics as ApiChannelStatistics)?.recentPosts?.length) && ( {Boolean((statistics as ApiChannelStatistics)?.recentPosts?.length) && (
<div className={styles.messages}> <Island className={styles.messages}>
<h2 className={styles.messagesTitle}>{lang('ChannelStats.Recent.Header')}</h2> <h2 className={styles.messagesTitle}>{lang('ChannelStats.Recent.Header')}</h2>
{(statistics as ApiChannelStatistics).recentPosts.map((postStatistic) => { {(statistics as ApiChannelStatistics).recentPosts.map((postStatistic) => {
@ -251,7 +254,7 @@ const Statistics = ({
return undefined; return undefined;
})} })}
</div> </Island>
)} )}
</div> </div>
); );

View File

@ -1,7 +1,5 @@
.root { .root {
margin-bottom: 1rem; padding: 0.5rem;
padding: 1rem 1.5rem;
border-bottom: 0.0625rem solid var(--color-borders);
} }
.header { .header {
@ -41,12 +39,12 @@
} }
.tableHeading { .tableHeading {
font-size: 0.9375rem; font-size: 0.875rem;
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
.tableValue { .tableValue {
font-size: 1.25rem; font-size: 1rem;
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
} }

View File

@ -26,6 +26,7 @@ type OwnProps = {
title?: string; title?: string;
autoFocus?: boolean; autoFocus?: boolean;
teactExperimentControlled?: boolean; teactExperimentControlled?: boolean;
noMargin?: boolean;
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
onChange?: (e: ChangeEvent<HTMLInputElement>) => void; onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
onInput?: (e: FormEvent<HTMLInputElement>) => void; onInput?: (e: FormEvent<HTMLInputElement>) => void;
@ -54,6 +55,7 @@ const InputText = ({
title, title,
autoFocus, autoFocus,
teactExperimentControlled, teactExperimentControlled,
noMargin,
onChange, onChange,
onInput, onInput,
onKeyPress, onKeyPress,
@ -71,6 +73,7 @@ const InputText = ({
disabled && 'disabled', disabled && 'disabled',
readOnly && 'disabled', readOnly && 'disabled',
labelText && 'with-label', labelText && 'with-label',
noMargin && 'no-margin',
className, className,
); );

View File

@ -163,11 +163,11 @@
} }
&-slideFadeAndroid { &-slideFadeAndroid {
--background-color: var(--color-background); --slide-background-color: var(--color-background);
> .Transition_slide { > .Transition_slide {
z-index: 0; z-index: 0;
background: var(--background-color); background: var(--slide-background-color);
} }
> .Transition_slide-to { > .Transition_slide-to {
@ -179,11 +179,11 @@
} }
&-slideFadeAndroidBackwards { &-slideFadeAndroidBackwards {
--background-color: var(--color-background); --slide-background-color: var(--color-background);
> .Transition_slide { > .Transition_slide {
z-index: 0; z-index: 0;
background: var(--background-color); background: var(--slide-background-color);
} }
> .Transition_slide-from { > .Transition_slide-from {
@ -277,12 +277,12 @@
} }
&-slideLayers { &-slideLayers {
--background-color: var(--color-background); --slide-background-color: var(--color-background);
background: black !important; background: black !important;
> .Transition_slide { > .Transition_slide {
background: var(--background-color); background: var(--slide-background-color);
} }
> .Transition_slide-to { > .Transition_slide-to {
@ -296,12 +296,12 @@
} }
&-slideLayersBackwards { &-slideLayersBackwards {
--background-color: var(--color-background); --slide-background-color: var(--color-background);
background: black !important; background: black !important;
> .Transition_slide { > .Transition_slide {
background: var(--background-color); background: var(--slide-background-color);
} }
> .Transition_slide-to { > .Transition_slide-to {
@ -317,8 +317,10 @@
} }
&-pushSlide { &-pushSlide {
--slide-background-color: var(--color-background);
> .Transition_slide { > .Transition_slide {
background: var(--color-background); background: var(--slide-background-color);
} }
> .Transition_slide-from { > .Transition_slide-from {
@ -343,8 +345,10 @@
} }
&-pushSlideBackwards { &-pushSlideBackwards {
--slide-background-color: var(--color-background);
> .Transition_slide { > .Transition_slide {
background: var(--color-background); background: var(--slide-background-color);
} }
> .Transition_slide-to { > .Transition_slide-to {

View File

@ -13,11 +13,13 @@ const useScrollNotch = ({
selector, selector,
isBottomNotch, isBottomNotch,
shouldHideTopNotch, shouldHideTopNotch,
onScrolled,
}: { }: {
containerRef: ElementRef<HTMLDivElement>; containerRef: ElementRef<HTMLDivElement>;
selector: string; selector: string;
isBottomNotch?: boolean; isBottomNotch?: boolean;
shouldHideTopNotch?: boolean; shouldHideTopNotch?: boolean;
onScrolled?: (isScrolled: boolean) => void;
}, deps: unknown[]) => { }, deps: unknown[]) => {
useLayoutEffect(() => { useLayoutEffect(() => {
const elements = containerRef.current?.querySelectorAll<HTMLElement>(selector); const elements = containerRef.current?.querySelectorAll<HTMLElement>(selector);
@ -29,6 +31,8 @@ const useScrollNotch = ({
const { scrollHeight, scrollTop, clientHeight } = target; const { scrollHeight, scrollTop, clientHeight } = target;
const isAtEnd = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD; const isAtEnd = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
onScrolled?.(isScrolled);
requestMutation(() => { requestMutation(() => {
if (!shouldHideTopNotch) { if (!shouldHideTopNotch) {
toggleExtraClass(target, 'scrolled', isScrolled); toggleExtraClass(target, 'scrolled', isScrolled);
@ -63,13 +67,18 @@ const useScrollNotch = ({
useEffect(() => { useEffect(() => {
const elements = containerRef.current?.querySelectorAll<HTMLElement>(selector); const elements = containerRef.current?.querySelectorAll<HTMLElement>(selector);
if (!elements?.length) return undefined; if (!elements?.length) {
onScrolled?.(false);
return undefined;
}
elements.forEach((el) => { elements.forEach((el) => {
const isScrolled = el.scrollTop > 0; const isScrolled = el.scrollTop > 0;
const { scrollHeight, scrollTop, clientHeight } = el; const { scrollHeight, scrollTop, clientHeight } = el;
const isAtEnd = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD; const isAtEnd = scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
onScrolled?.(isScrolled);
requestMutation(() => { requestMutation(() => {
if (!shouldHideTopNotch) { if (!shouldHideTopNotch) {
toggleExtraClass(el, 'scrolled', isScrolled); toggleExtraClass(el, 'scrolled', isScrolled);

Some files were not shown because too many files have changed in this diff Show More