style(create-chat): Dawn grouped form with a mono server and one native layout without the hero

This commit is contained in:
heaven 2026-06-04 00:59:55 +03:00
parent 4c6f662939
commit 7ea273eca8
3 changed files with 70 additions and 124 deletions

View file

@ -8,11 +8,11 @@ type Props = {
onClose: () => void; onClose: () => void;
}; };
// Thin shell around the shared `CreateChat` form. The legacy // Thin shell around the shared `CreateChat` form. This curtain is the
// `/direct/_create` route renders the same component with a Page/ // canonical native new-chat surface; the `/direct/_create` route renders
// PageHero shell; here we only feed it the `onClose` callback and the // the same grouped block under a back-chevron PageHeader. Here we only
// tighter `gap='400'` rhythm so the form fits comfortably under the // feed it the `onClose` callback and the tighter `gap='400'` rhythm so the
// header. // form fits comfortably under the header.
export function InlineNewChatForm({ onClose }: Props) { export function InlineNewChatForm({ onClose }: Props) {
return <CreateChat gap="400" onCreated={onClose} />; return <CreateChat gap="400" onCreated={onClose} />;
} }

View file

@ -1,22 +1,11 @@
import { import { Box, Button, color, Icon, Icons, Input, Spinner, Switch, Text, toRem } from 'folds';
Box,
Button,
color,
config,
Icon,
Icons,
Input,
Spinner,
Switch,
Text,
toRem,
} from 'folds';
import React, { FormEventHandler, useCallback, useState } from 'react'; import React, { FormEventHandler, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk'; import { ICreateRoomStateEvent, MatrixError, Preset, Visibility } from 'matrix-js-sdk';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { SettingTile } from '../../components/setting-tile'; import { SettingTile } from '../../components/setting-tile';
import { SequenceCard } from '../../components/sequence-card'; import { SettingsSection } from '../settings/SettingsSection';
import { Mono, SectionLabel } from '../settings/styles.css';
import { import {
addRoomIdToMDirect, addRoomIdToMDirect,
getDMRoomFor, getDMRoomFor,
@ -136,17 +125,25 @@ export function CreateChat({ defaultUserId, onCreated, gap = '500' }: CreateChat
return ( return (
<Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap={gap}> <Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap={gap}>
<Box direction="Column" gap="100"> <Box direction="Column" gap="200">
<Text as="span" className={SectionLabel}>
{t('Direct.address')}
</Text>
<Box direction="Row" wrap="Wrap" gap="200"> <Box direction="Row" wrap="Wrap" gap="200">
<Box grow="Yes" style={{ minWidth: toRem(120) }} direction="Column" gap="100"> <Box grow="Yes" style={{ minWidth: toRem(120) }} direction="Column" gap="100">
<Text size="L400">{t('Direct.username')}</Text>
<Input <Input
defaultValue={defaultUsername} defaultValue={defaultUsername}
placeholder={t('Direct.username_placeholder')} placeholder={t('Direct.username_placeholder')}
name="usernameInput" name="usernameInput"
variant="SurfaceVariant" variant="Background"
size="500" size="500"
radii="400" radii="400"
outlined
before={
<Text as="span" size="B500" className={Mono} style={{ opacity: 0.5 }}>
@
</Text>
}
required required
autoFocus autoFocus
autoComplete="off" autoComplete="off"
@ -154,16 +151,17 @@ export function CreateChat({ defaultUserId, onCreated, gap = '500' }: CreateChat
/> />
</Box> </Box>
<Box shrink="No" style={{ minWidth: toRem(150) }} direction="Column" gap="100"> <Box shrink="No" style={{ minWidth: toRem(150) }} direction="Column" gap="100">
<Text size="L400">{t('Direct.server')}</Text>
<Input <Input
defaultValue={ defaultValue={
defaultServer && defaultServer !== userServer ? defaultServer : undefined defaultServer && defaultServer !== userServer ? defaultServer : undefined
} }
placeholder={userServer} placeholder={userServer}
name="serverInput" name="serverInput"
variant="SurfaceVariant" variant="Background"
size="500" size="500"
radii="400" radii="400"
outlined
className={Mono}
autoComplete="off" autoComplete="off"
disabled={disabled} disabled={disabled}
/> />
@ -171,50 +169,40 @@ export function CreateChat({ defaultUserId, onCreated, gap = '500' }: CreateChat
</Box> </Box>
{invalidUserId && ( {invalidUserId && (
<Box style={{ color: color.Critical.Main }} alignItems="Center" gap="100"> <Box style={{ color: color.Critical.Main }} alignItems="Center" gap="100">
<Icon src={Icons.Warning} filled size="50" /> <Icon src={Icons.Warning} size="50" />
<Text size="T200" style={{ color: color.Critical.Main }}> <Text size="T200" style={{ color: color.Critical.Main }}>
<b>{t('Direct.invalid_user_id')}</b> {t('Direct.invalid_user_id')}
</Text> </Text>
</Box> </Box>
)} )}
</Box> </Box>
<Box shrink="No" direction="Column" gap="100"> <SettingsSection label={t('Direct.options')}>
<Text size="L400">{t('Direct.options')}</Text> <SettingTile
<SequenceCard title={t('Direct.e2e_encryption')}
style={{ padding: config.space.S300 }} description={t('Direct.e2e_encryption_desc')}
variant="SurfaceVariant" after={
direction="Column" <Switch
gap="500" variant="Primary"
> value={encryption}
<SettingTile onChange={setEncryption}
title={t('Direct.e2e_encryption')} disabled={disabled}
description={t('Direct.e2e_encryption_desc')} />
after={ }
<Switch />
variant="Primary" </SettingsSection>
value={encryption}
onChange={setEncryption}
disabled={disabled}
/>
}
/>
</SequenceCard>
</Box>
{error && ( {error && (
<Box style={{ color: color.Critical.Main }} alignItems="Center" gap="200"> <Box style={{ color: color.Critical.Main }} alignItems="Center" gap="200">
<Icon src={Icons.Warning} filled size="100" /> <Icon src={Icons.Warning} size="100" />
<Text size="T300" style={{ color: color.Critical.Main }}> <Text size="T300" style={{ color: color.Critical.Main }}>
<b> {(() => {
{(() => { if (error instanceof MatrixError && error.name === ErrorCode.M_LIMIT_EXCEEDED) {
if (error instanceof MatrixError && error.name === ErrorCode.M_LIMIT_EXCEEDED) { const ra = error.data?.retry_after_ms;
const ra = error.data?.retry_after_ms; return t('Direct.rate_limited', {
return t('Direct.rate_limited', { minutes: millisecondsToMinutes(typeof ra === 'number' ? ra : 0),
minutes: millisecondsToMinutes(typeof ra === 'number' ? ra : 0), });
}); }
} return error.message;
return error.message; })()}
})()}
</b>
</Text> </Text>
</Box> </Box>
)} )}

View file

@ -7,24 +7,13 @@ import { getDirectCreateSearchParams } from '../../pathSearchParam';
import { getDirectRoomPath } from '../../pathUtils'; import { getDirectRoomPath } from '../../pathUtils';
import { getDMRoomFor } from '../../../utils/matrix'; import { getDMRoomFor } from '../../../utils/matrix';
import { useDirectRooms } from './useDirectRooms'; import { useDirectRooms } from './useDirectRooms';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
import {
Page,
PageContent,
PageContentCenter,
PageHeader,
PageHero,
PageHeroSection,
} from '../../../components/page';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { CreateChat } from '../../../features/create-chat'; import { CreateChat } from '../../../features/create-chat';
import { isNativePlatform } from '../../../utils/capacitor';
export function DirectCreate() { export function DirectCreate() {
const { t } = useTranslation(); const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const screenSize = useScreenSizeContext();
const native = isNativePlatform();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@ -41,64 +30,33 @@ export function DirectCreate() {
} }
}, [mx, navigate, directs, userId]); }, [mx, navigate, directs, userId]);
// Native phone (Capacitor + Mobile screen): compact layout — title goes into // One Dawn layout for every size: a left-aligned back-chevron header over the
// the back-arrow header, form sits directly below with light padding, no // grouped CreateChat block in a padded Scroll. No centered hero — the curtain
// oversized hero whitespace. The on-screen keyboard otherwise pushed the // (InlineNewChatForm) is the canonical native new-chat surface, and the
// centered hero layout into a visibly long scrollable area. Native tablets, // keyboard no longer fights an oversized hero on short native viewports.
// desktop, and web (any size) keep the original hero-section layout.
if (native && screenSize === ScreenSize.Mobile) {
return (
<Page>
<PageHeader balance outlined={false}>
<Box grow="Yes" alignItems="Center" gap="200">
<BackRouteHandler>
{(onBack) => (
<IconButton onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}
</BackRouteHandler>
<Text size="H4" truncate>
{t('Direct.create_chat')}
</Text>
</Box>
</PageHeader>
<Box grow="Yes">
<Scroll size="0" hideTrack visibility="Hover">
<Box style={{ padding: config.space.S400 }}>
<CreateChat defaultUserId={userId} />
</Box>
</Scroll>
</Box>
</Page>
);
}
return ( return (
<Page> <Page>
{screenSize === ScreenSize.Mobile && ( <PageHeader balance outlined={false}>
<PageHeader balance outlined={false}> <Box grow="Yes" alignItems="Center" gap="200">
<Box grow="Yes" alignItems="Center" gap="200"> <BackRouteHandler>
<BackRouteHandler> {(onBack) => (
{(onBack) => ( <IconButton onClick={onBack}>
<IconButton onClick={onBack}> <Icon src={Icons.ArrowLeft} />
<Icon src={Icons.ArrowLeft} /> </IconButton>
</IconButton> )}
)} </BackRouteHandler>
</BackRouteHandler> <Text size="H4" truncate>
</Box> {t('Direct.create_chat')}
</PageHeader> </Text>
)} </Box>
</PageHeader>
<Box grow="Yes"> <Box grow="Yes">
<Scroll hideTrack visibility="Hover"> <Scroll hideTrack visibility="Hover">
<PageContent> <PageContent>
<PageContentCenter> <PageContentCenter>
<PageHeroSection> <Box style={{ paddingTop: config.space.S200 }}>
<Box direction="Column" gap="700"> <CreateChat defaultUserId={userId} />
<PageHero title={t('Direct.create_chat')} /> </Box>
<CreateChat defaultUserId={userId} />
</Box>
</PageHeroSection>
</PageContentCenter> </PageContentCenter>
</PageContent> </PageContent>
</Scroll> </Scroll>