Compare commits

..
135 changed files with 403 additions and 2610 deletions

View file

@ -47,10 +47,6 @@ handoff states which config differs and the installer decides. From Yggdrasil th
not resolve: pass `LIVE_CURL_ARGS="--resolve chat.gamecult.org:443:127.0.0.1"`.
The folder, with the three pins (`SHA256SUMS` sha256, `BUILD-INFO` sha256, lockfile sha256) and
the node and npm versions, is what the install request names.
Every build stamps its commit into `index.html` (`scripts/buildSha.js`): `<meta name="build-sha" content="<40-hex>">`,
with `-dirty` appended when the tree had uncommitted changes and `unknown` when there was no git. Where git governs the tree it decides: an explicit `BUILD_SHA` naming a different commit than HEAD stamps `<sha>-mismatch`, a git failure stamps `unknown`. A full-sha `BUILD_SHA` stands only for a tree git does not govern (a source archive) or cannot read (unusable git, for example a dubious-ownership refusal). The handoff
build refuses a dist whose stamp differs from the sha it was asked for, and prints the stamp in its block.
To read it from anywhere: `curl -s https://chat.gamecult.org/ | grep -o 'build-sha" content="[^"]*'`.
Before handing off:

View file

@ -1,56 +0,0 @@
// The commit a bundle was built from, stamped into its index.html as <meta name="build-sha">, so a built
// folder says what it is without a build log: `grep -o 'build-sha" content="[^"]*' dist/index.html`.
// Where git governs the tree (REPO_DIR is a repository root) git decides: HEAD, suffixed -dirty when the tree has
// uncommitted changes, or `<BUILD_SHA>-mismatch` when an explicit BUILD_SHA names a different commit, so a stamp
// never vouches for code it does not hold; a git failure there is 'unknown'. Where git does not govern (a source
// archive), a full-sha BUILD_SHA stands, else 'unknown'.
import { execFileSync } from 'child_process';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
const REPO_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..');
// Vite writes vite.config.js.timestamp-*.mjs next to the config while it loads it; that file is the
// build's own, not source, so an UNTRACKED one must not mark the tree dirty. A tracked file of that name,
// or any other change, still does. Filtering status lines (not a pathspec) is what keeps it untracked-only.
const VITE_TEMP = /^\?\? (?:.*\/)?vite\.config\.[a-z]+\.timestamp-\d+-[0-9a-f]+\.mjs$/;
export const gitIn =
(cwd) =>
(...args) =>
execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
const FULL_SHA = /^[0-9a-f]{40}$/;
export const readBuildSha = (env = process.env, run = gitIn(REPO_DIR)) => {
const fromEnv = (env.BUILD_SHA ?? '').trim();
const explicit = FULL_SHA.test(fromEnv) ? fromEnv : null;
// Git governs this tree only when REPO_DIR is its root: a source archive unpacked inside some other
// repository has no git of its own, and that repository's HEAD says nothing about it.
let governed;
try {
governed = run('rev-parse', '--show-prefix').trim() === '';
} catch {
governed = false;
}
if (!governed) return explicit ?? 'unknown';
// Git governs and answers: from here a failure is 'unknown', never the bare explicit sha.
try {
const head = run('rev-parse', 'HEAD').trim();
if (!FULL_SHA.test(head)) return 'unknown';
if (explicit && explicit !== head) return `${explicit}-mismatch`;
const dirty = run('status', '--porcelain', '-uall', '--', '.')
.split('\n')
.some((l) => l.trim() !== '' && !VITE_TEMP.test(l));
return dirty ? `${head}-dirty` : head;
} catch {
return 'unknown';
}
};
export const buildShaPlugin = (sha = readBuildSha()) => ({
name: 'ratatoskr-build-sha',
transformIndexHtml: () => [
{ tag: 'meta', attrs: { name: 'build-sha', content: sha }, injectTo: 'head-prepend' },
],
});

View file

@ -1,170 +0,0 @@
import { execFileSync } from 'child_process';
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { afterEach, describe, expect, it } from 'vitest';
import { buildShaPlugin, gitIn, readBuildSha } from './buildSha';
const SHA = 'b6b1d522f1c0a9d3e2b4c5d6e7f8091a2b3c4d5e';
const gitAt =
(head, status = '') =>
(cmd, ...args) =>
cmd === 'rev-parse' ? (args[0] === '--show-prefix' ? '\n' : `${head}\n`) : status;
describe('readBuildSha', () => {
it('reads the checkout it is built from', () => {
expect(readBuildSha({}, gitAt(SHA))).toBe(SHA);
});
it('marks a tree with uncommitted changes, so a stamp never vouches for code it does not hold', () => {
expect(readBuildSha({}, gitAt(SHA, ' M src/x.ts\n'))).toBe(`${SHA}-dirty`);
});
it('lets BUILD_SHA stand in where there is no git, and ignores a value that is not a full sha', () => {
const noGit = () => {
throw new Error('not a git repository');
};
expect(readBuildSha({ BUILD_SHA: SHA }, noGit)).toBe(SHA);
expect(readBuildSha({ BUILD_SHA: SHA }, gitAt(SHA))).toBe(SHA);
// a different HEAD is a mismatch, not a clean stamp of the explicit sha
expect(readBuildSha({ BUILD_SHA: SHA }, gitAt('c'.repeat(40)))).toBe(`${SHA}-mismatch`);
// a value that is not a lowercase full sha is ignored
['b6b1d52', SHA.toUpperCase(), `${SHA}0`].forEach((v) =>
expect(readBuildSha({ BUILD_SHA: v }, noGit)).toBe('unknown')
);
// git that answers but cannot say what the tree holds fails closed
const brokenStatus = (cmd, ...args) => {
if (cmd === 'status') throw new Error('index file smaller than expected');
return args[0] === '--show-prefix' ? '\n' : `${SHA}\n`;
};
expect(readBuildSha({ BUILD_SHA: SHA }, brokenStatus)).toBe('unknown');
expect(readBuildSha({}, brokenStatus)).toBe('unknown');
expect(readBuildSha({}, gitAt('nope'))).toBe('unknown');
// inside another repository's subfolder git does not govern: the explicit sha stands
const nested = (cmd, ...args) => (args[0] === '--show-prefix' ? 'vendor/ratatoskr/\n' : SHA);
expect(readBuildSha({ BUILD_SHA: SHA }, nested)).toBe(SHA);
expect(readBuildSha({}, nested)).toBe('unknown');
expect(readBuildSha({ BUILD_SHA: 'main' }, noGit)).toBe('unknown');
});
it("says 'unknown' rather than failing the build when git is absent", () => {
expect(
readBuildSha({}, () => {
throw new Error('spawn git ENOENT');
})
).toBe('unknown');
});
});
describe('readBuildSha against a real repository', () => {
const made = [];
afterEach(() => {
made.splice(0).forEach((d) => rmSync(d, { recursive: true, force: true }));
});
const repo = () => {
const dir = mkdtempSync(join(tmpdir(), 'buildsha-'));
made.push(dir);
const g = (...a) => execFileSync('git', a, { cwd: dir, stdio: 'ignore' });
g('init', '-q');
mkdirSync(join(dir, 'src'));
writeFileSync(join(dir, 'src', 'a.ts'), 'export {};\n');
g('add', '.');
g('-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-qm', 'x');
return dir;
};
it("stays clean with vite's temp config file present, and is dirty with a stray source file", () => {
const dir = repo();
const run = gitIn(dir);
writeFileSync(join(dir, 'vite.config.js.timestamp-1700000000-abc123.mjs'), '');
expect(readBuildSha({}, run)).toMatch(/^[0-9a-f]{40}$/);
writeFileSync(join(dir, 'src', 'zz-stray.ts'), '');
expect(readBuildSha({}, run)).toMatch(/^[0-9a-f]{40}-dirty$/);
});
});
describe('readBuildSha dirty rules against a real repository', () => {
const made2 = [];
afterEach(() => {
made2.splice(0).forEach((d) => rmSync(d, { recursive: true, force: true }));
});
const TEMP = 'vite.config.js.timestamp-1700000000-abc123.mjs';
const repo2 = (tracked = []) => {
const dir = mkdtempSync(join(tmpdir(), 'buildsha-'));
made2.push(dir);
const g = (...a) => execFileSync('git', a, { cwd: dir, stdio: 'ignore' });
g('init', '-q');
mkdirSync(join(dir, 'src'));
writeFileSync(join(dir, 'src', 'a.ts'), 'export {};\n');
tracked.forEach((f) => writeFileSync(join(dir, f), 'x\n'));
g('add', '.');
g('-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-qm', 'x');
return {
dir,
head: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: dir, encoding: 'utf8' }).trim(),
};
};
it('does not let an explicit BUILD_SHA mask a dirty tree', () => {
const { dir, head } = repo2();
const run = gitIn(dir);
expect(readBuildSha({ BUILD_SHA: head }, run)).toBe(head);
writeFileSync(join(dir, 'src', 'zz-stray.ts'), '');
expect(readBuildSha({ BUILD_SHA: head }, run)).toBe(`${head}-dirty`);
});
it('lets git speak only for a tree whose root it is: an archive unpacked inside another repository keeps its explicit sha', () => {
const { dir } = repo2();
const inner = join(dir, 'vendor', 'ratatoskr');
mkdirSync(inner, { recursive: true });
writeFileSync(join(inner, 'x.ts'), '');
expect(readBuildSha({ BUILD_SHA: SHA }, gitIn(inner))).toBe(SHA);
expect(readBuildSha({}, gitIn(inner))).toBe('unknown');
});
it("notices an untracked .mjs that is not vite's temp file", () => {
const { dir, head } = repo2();
writeFileSync(join(dir, 'foo.mjs'), '');
expect(readBuildSha({}, gitIn(dir))).toBe(`${head}-dirty`);
rmSync(join(dir, 'foo.mjs'));
// only vite's exact shape is its own: vite.config.<ext>.timestamp-<digits>-<hex>.mjs
[
'vite.config.js.timestamp-1-ab.mjs.bak',
'vite.config.js.timestamp-x-ab.mjs',
'vite.config.js.timestamp-1-zz.mjs',
'vite.config.a.b.timestamp-1-ab.mjs',
'vite.config.js.timestamp-1-ab-c.mjs',
].forEach((name) => {
writeFileSync(join(dir, name), '');
expect(readBuildSha({}, gitIn(dir)), name).toBe(`${head}-dirty`);
rmSync(join(dir, name));
});
expect(readBuildSha({}, gitIn(dir))).toBe(head);
});
it('treats a stray source file as dirty even beside the vite temp file, in the root or a subfolder', () => {
const { dir, head } = repo2();
const run = gitIn(dir);
writeFileSync(join(dir, TEMP), '');
writeFileSync(join(dir, 'src', TEMP), '');
expect(readBuildSha({}, run)).toBe(head);
writeFileSync(join(dir, 'src', 'zz-stray.ts'), '');
expect(readBuildSha({}, run)).toBe(`${head}-dirty`);
});
it('marks a tracked file of the temp name dirty once it changes', () => {
const { dir, head } = repo2([TEMP]);
const run = gitIn(dir);
expect(readBuildSha({}, run)).toBe(head);
writeFileSync(join(dir, TEMP), 'changed\n');
expect(readBuildSha({}, run)).toBe(`${head}-dirty`);
});
it('marks an untracked file in a new folder dirty (status lists files, not the folder)', () => {
const { dir, head } = repo2();
mkdirSync(join(dir, 'new'));
writeFileSync(join(dir, 'new', TEMP), '');
expect(readBuildSha({}, gitIn(dir))).toBe(head);
writeFileSync(join(dir, 'new', 'b.ts'), '');
expect(readBuildSha({}, gitIn(dir))).toBe(`${head}-dirty`);
});
});
describe('buildShaPlugin', () => {
it("hands vite a meta tag for the page's head carrying the sha", () => {
const plugin = buildShaPlugin(SHA);
expect(plugin.transformIndexHtml()).toEqual([
{ tag: 'meta', attrs: { name: 'build-sha', content: SHA }, injectTo: 'head-prepend' },
]);
});
});

View file

@ -16,7 +16,6 @@ import {
} from 'folds';
import { MatrixError } from 'matrix-js-sdk';
import { Cursor } from '../plugins/text-area';
import { PhoneHit } from '../styles/PhoneHit.css';
import { syntaxErrorPosition } from '../utils/dom';
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
import { Page, PageHeader } from './page';
@ -295,7 +294,7 @@ export function AccountDataEditor({
</Chip>
</Box>
<Box shrink="No">
<IconButton onClick={requestClose} variant="Surface" className={PhoneHit}>
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -30,7 +30,6 @@ import {
} from '../hooks/useKeyBackup';
import { stopPropagation } from '../utils/keyboard';
import { useRestoreBackupOnVerification } from '../hooks/useRestoreBackupOnVerification';
import { PhoneHit } from '../styles/PhoneHit.css';
type BackupStatusProps = {
enabled: boolean;
@ -178,7 +177,6 @@ export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) {
<BackupSyncing count={remainingSession} />
)}
<IconButton
className={PhoneHit}
aria-pressed={!!menuCords}
size="300"
variant="Surface"

View file

@ -30,7 +30,6 @@ import {
} from '../hooks/useVerificationRequest';
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
import { ContainerColor } from '../styles/ContainerColor.css';
import { PhoneHit } from '../styles/PhoneHit.css';
const DialogHeaderStyles: CSSProperties = {
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
@ -262,7 +261,7 @@ export function DeviceVerification({ request, onExit }: DeviceVerificationProps)
<Box grow="Yes">
<Text size="H4">Device Verification</Text>
</Box>
<IconButton className={PhoneHit} size="300" radii="300" onClick={handleCancel}>
<IconButton size="300" radii="300" onClick={handleCancel}>
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -25,7 +25,6 @@ import { ActionUIA, ActionUIAFlowsLoader } from './ActionUIA';
import { useMatrixClient } from '../hooks/useMatrixClient';
import { useAlive } from '../hooks/useAlive';
import { UseStateProvider } from './UseStateProvider';
import { PhoneHit } from '../styles/PhoneHit.css';
type UIACallback<T> = (
authDict: AuthDict | null
@ -302,7 +301,7 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
<Box grow="Yes">
<Text size="H4">Setup Device Verification</Text>
</Box>
<IconButton className={PhoneHit} size="300" radii="300" onClick={onCancel}>
<IconButton size="300" radii="300" onClick={onCancel}>
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@ -337,7 +336,7 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
<Box grow="Yes">
<Text size="H4">Reset Device Verification</Text>
</Box>
<IconButton className={PhoneHit} size="300" radii="300" onClick={onCancel}>
<IconButton size="300" radii="300" onClick={onCancel}>
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -27,7 +27,6 @@ import { AsyncStatus } from '../../hooks/useAsyncCallback';
import { useZoom } from '../../hooks/useZoom';
import { createPage, usePdfDocumentLoader, usePdfJSLoader } from '../../plugins/pdfjs-dist';
import { stopPropagation } from '../../utils/keyboard';
import { PhoneHit, PhoneHitSpread24 } from '../../styles/PhoneHit.css';
export type PdfViewerProps = {
name: string;
@ -109,7 +108,7 @@ export const PdfViewer = as<'div', PdfViewerProps>(
<Box className={classNames(css.PdfViewer, className)} direction="Column" {...props} ref={ref}>
<Header className={css.PdfViewerHeader} size="400">
<Box grow="Yes" alignItems="Center" gap="200">
<IconButton className={PhoneHit} size="300" radii="300" onClick={requestClose}>
<IconButton size="300" radii="300" onClick={requestClose}>
<Icon size="50" src={Icons.ArrowLeft} />
</IconButton>
<Text size="T300" truncate>
@ -118,7 +117,6 @@ export const PdfViewer = as<'div', PdfViewerProps>(
</Box>
<Box shrink="No" alignItems="Center" gap="200">
<IconButton
className={PhoneHitSpread24}
variant={zoom < 1 ? 'Success' : 'SurfaceVariant'}
outlined={zoom < 1}
size="300"
@ -132,7 +130,6 @@ export const PdfViewer = as<'div', PdfViewerProps>(
<Text size="B300">{Math.round(zoom * 100)}%</Text>
</Chip>
<IconButton
className={PhoneHitSpread24}
variant={zoom > 1 ? 'Success' : 'SurfaceVariant'}
outlined={zoom > 1}
size="300"

View file

@ -13,7 +13,6 @@ import {
IconButton,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { PhoneHit } from '../styles/PhoneHit.css';
export type UIAFlowOverlayProps = {
currentStep: number;
@ -54,7 +53,6 @@ export function UIAFlowOverlay({
>
{(anchorRef) => (
<IconButton
className={PhoneHit}
ref={anchorRef}
variant="Critical"
size="300"

View file

@ -0,0 +1,84 @@
import React, { useState } from 'react';
import FocusTrap from 'focus-trap-react';
import {
config,
Icon,
IconButton,
Icons,
Line,
Modal,
Overlay,
OverlayBackdrop,
OverlayCenter,
} from 'folds';
import { CustomEditor, useEditor } from './Editor';
import { Toolbar } from './Toolbar';
import { stopPropagation } from '../../utils/keyboard';
export function EditorPreview() {
const [open, setOpen] = useState(false);
const editor = useEditor();
const [toolbar, setToolbar] = useState(false);
return (
<>
<IconButton variant="SurfaceVariant" onClick={() => setOpen(!open)}>
<Icon src={Icons.BlockQuote} />
</IconButton>
<Overlay open={open} backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: () => setOpen(false),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Modal size="500">
<div style={{ padding: config.space.S400 }}>
<CustomEditor
editor={editor}
placeholder="Send a message..."
before={
<IconButton variant="SurfaceVariant" size="300" radii="300">
<Icon src={Icons.PlusCircle} />
</IconButton>
}
after={
<>
<IconButton
variant="SurfaceVariant"
size="300"
radii="300"
onClick={() => setToolbar(!toolbar)}
aria-pressed={toolbar}
>
<Icon src={toolbar ? Icons.AlphabetUnderline : Icons.Alphabet} />
</IconButton>
<IconButton variant="SurfaceVariant" size="300" radii="300">
<Icon src={Icons.Smile} />
</IconButton>
<IconButton variant="SurfaceVariant" size="300" radii="300">
<Icon src={Icons.Send} />
</IconButton>
</>
}
bottom={
toolbar && (
<div>
<Line variant="SurfaceVariant" size="300" />
<Toolbar />
</div>
)
}
/>
</div>
</Modal>
</FocusTrap>
</OverlayCenter>
</Overlay>
</>
);
}

View file

@ -36,7 +36,6 @@ import { KeySymbol } from '../../utils/key-symbol';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { stopPropagation } from '../../utils/keyboard';
import { PhoneHit, PhoneHitRoom36, PhoneHitSpread36 } from '../../styles/PhoneHit.css';
function BtnTooltip({ text, shortCode }: { text: string; shortCode?: string }) {
return (
@ -73,7 +72,6 @@ export function MarkButton({ format, icon, tooltip }: MarkButtonProps) {
<TooltipProvider tooltip={tooltip} delay={500}>
{(triggerRef) => (
<IconButton
className={PhoneHitSpread36}
ref={triggerRef}
variant="SurfaceVariant"
onClick={handleClick}
@ -106,7 +104,6 @@ export function BlockButton({ format, icon, tooltip }: BlockButtonProps) {
<TooltipProvider tooltip={tooltip} delay={500}>
{(triggerRef) => (
<IconButton
className={PhoneHitSpread36}
ref={triggerRef}
variant="SurfaceVariant"
onClick={handleClick}
@ -166,7 +163,6 @@ export function HeadingBlockButton() {
>
{(triggerRef) => (
<IconButton
className={PhoneHitRoom36}
ref={triggerRef}
onClick={() => handleMenuSelect(1)}
size="400"
@ -182,7 +178,6 @@ export function HeadingBlockButton() {
>
{(triggerRef) => (
<IconButton
className={PhoneHitRoom36}
ref={triggerRef}
onClick={() => handleMenuSelect(2)}
size="400"
@ -198,7 +193,6 @@ export function HeadingBlockButton() {
>
{(triggerRef) => (
<IconButton
className={PhoneHitRoom36}
ref={triggerRef}
onClick={() => handleMenuSelect(3)}
size="400"
@ -214,7 +208,6 @@ export function HeadingBlockButton() {
}
>
<IconButton
className={PhoneHit}
style={{ width: 'unset' }}
variant="SurfaceVariant"
onClick={handleMenuOpen}
@ -246,7 +239,6 @@ export function ExitFormatting({ tooltip }: ExitFormattingProps) {
<TooltipProvider tooltip={tooltip} delay={500}>
{(triggerRef) => (
<IconButton
className={PhoneHit}
ref={triggerRef}
variant="SurfaceVariant"
onClick={handleClick}
@ -352,7 +344,6 @@ export function Toolbar() {
>
{(triggerRef) => (
<IconButton
className={PhoneHit}
ref={triggerRef}
variant="SurfaceVariant"
onClick={() => setIsMarkdown(!isMarkdown)}

View file

@ -14,7 +14,6 @@ import {
} from 'folds';
import classNames from 'classnames';
import * as css from './styles.css';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function Sidebar({ children }: { children: ReactNode }) {
return (
@ -69,7 +68,6 @@ function SidebarBtn<T extends string>({
>
{(ref) => (
<IconButton
className={PhoneHit}
aria-pressed={active}
aria-labelledby={`SidebarStackItem-${id}-label`}
ref={ref}

View file

@ -24,7 +24,6 @@ import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../hooks/useSpace';
import { getMouseEventCords } from '../../utils/dom';
import { PhoneHit } from '../../styles/PhoneHit.css';
export type EventReadersProps = {
room: Room;
@ -53,7 +52,7 @@ export const EventReaders = as<'div', EventReadersProps>(
<Box grow="Yes">
<Text size="H3">Seen by</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={requestClose}>
<IconButton size="300" onClick={requestClose}>
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -2,7 +2,6 @@ import React from 'react';
import classNames from 'classnames';
import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
import * as css from './ImageEditor.css';
import { PhoneHit } from '../../styles/PhoneHit.css';
export type ImageEditorProps = {
name: string;
@ -25,7 +24,7 @@ export const ImageEditor = as<'div', ImageEditorProps>(
>
<Header className={css.ImageEditorHeader} size="400">
<Box grow="Yes" alignItems="Center" gap="200">
<IconButton className={PhoneHit} size="300" radii="300" onClick={requestClose}>
<IconButton size="300" radii="300" onClick={requestClose}>
<Icon size="50" src={Icons.ArrowLeft} />
</IconButton>
<Text size="T300" truncate>

View file

@ -1,7 +1,6 @@
import React from 'react';
import { Box, IconButton, Text, Icon, Icons, Scroll, Chip } from 'folds';
import { PackAddress } from '../../plugins/custom-emoji';
import { PhoneHit } from '../../styles/PhoneHit.css';
import { Page, PageHeader, PageContent } from '../page';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { RoomImagePack } from './RoomImagePack';
@ -30,7 +29,7 @@ export function ImagePackView({ address, requestClose }: ImagePackViewProps) {
</Chip>
</Box>
<Box shrink="No">
<IconButton onClick={requestClose} variant="Surface" className={PhoneHit}>
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -7,7 +7,6 @@ import * as css from './ImageViewer.css';
import { useZoom } from '../../hooks/useZoom';
import { usePan } from '../../hooks/usePan';
import { downloadMedia } from '../../utils/matrix';
import { PhoneHit, PhoneHitSpread24 } from '../../styles/PhoneHit.css';
export type ImageViewerProps = {
alt: string;
@ -34,7 +33,7 @@ export const ImageViewer = as<'div', ImageViewerProps>(
>
<Header className={css.ImageViewerHeader} size="400">
<Box grow="Yes" alignItems="Center" gap="200">
<IconButton className={PhoneHit} size="300" radii="300" onClick={requestClose}>
<IconButton size="300" radii="300" onClick={requestClose}>
<Icon size="50" src={Icons.ArrowLeft} />
</IconButton>
<Text size="T300" truncate>
@ -43,7 +42,6 @@ export const ImageViewer = as<'div', ImageViewerProps>(
</Box>
<Box shrink="No" alignItems="Center" gap="200">
<IconButton
className={PhoneHitSpread24}
variant={zoom < 1 ? 'Success' : 'SurfaceVariant'}
outlined={zoom < 1}
size="300"
@ -57,7 +55,6 @@ export const ImageViewer = as<'div', ImageViewerProps>(
<Text size="B300">{Math.round(zoom * 100)}%</Text>
</Chip>
<IconButton
className={PhoneHitSpread24}
variant={zoom > 1 ? 'Success' : 'SurfaceVariant'}
outlined={zoom > 1}
size="300"

View file

@ -42,7 +42,6 @@ import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { BreakWord } from '../../styles/Text.css';
import { useAlive } from '../../hooks/useAlive';
import { PhoneHit } from '../../styles/PhoneHit.css';
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
limit: 1000,
@ -174,7 +173,7 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} size="300" radii="300" onClick={requestClose}>
<IconButton size="300" radii="300" onClick={requestClose}>
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -20,7 +20,6 @@ import { stopPropagation } from '../../utils/keyboard';
import { isRoomAlias, isRoomId } from '../../utils/matrix';
import { parseMatrixToRoom, parseMatrixToRoomEvent, testMatrixTo } from '../../plugins/matrix-to';
import { tryDecodeURIComponent } from '../../utils/dom';
import { PhoneHit } from '../../styles/PhoneHit.css';
type JoinAddressProps = {
onOpen: (roomIdOrAlias: string, via?: string[], eventId?: string) => void;
@ -83,7 +82,7 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
<Box grow="Yes">
<Text size="H4">Join with Address</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -20,7 +20,6 @@ import { MatrixError } from 'matrix-js-sdk';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { stopPropagation } from '../../utils/keyboard';
import { PhoneHit } from '../../styles/PhoneHit.css';
type LeaveRoomPromptProps = {
roomId: string;
@ -69,7 +68,7 @@ export function LeaveRoomPrompt({ roomId, onDone, onCancel }: LeaveRoomPromptPro
<Box grow="Yes">
<Text size="H4">Leave Room</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -20,7 +20,6 @@ import { MatrixError } from 'matrix-js-sdk';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { stopPropagation } from '../../utils/keyboard';
import { PhoneHit } from '../../styles/PhoneHit.css';
type LeaveSpacePromptProps = {
roomId: string;
@ -69,7 +68,7 @@ export function LeaveSpacePrompt({ roomId, onDone, onCancel }: LeaveSpacePromptP
<Box grow="Yes">
<Text size="H4">Leave Space</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -2,7 +2,6 @@ import { Badge, Box, Icon, IconButton, Icons, Spinner, Text, as, toRem } from 'f
import React, { ReactNode, useCallback } from 'react';
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import FileSaver from 'file-saver';
import { PhoneHit } from '../../styles/PhoneHit.css';
import { mimeTypeToExt } from '../../utils/mimeTypes';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
@ -49,7 +48,6 @@ export function FileDownloadButton({ filename, url, mimeType, encInfo }: FileDow
variant={hasError ? 'Critical' : 'SurfaceVariant'}
size="300"
radii="300"
className={PhoneHit}
>
{downloading ? (
<Spinner size="100" variant={hasError ? 'Critical' : 'Secondary'} />

View file

@ -1,9 +1,6 @@
import { createVar, style } from '@vanilla-extract/css';
import { DefaultReset, FocusOutline, color, config, toRem } from 'folds';
import { MOBILE_BREAKPOINT } from '../../hooks/useScreenSize';
import { PHONE_TAP } from '../../styles/PhoneHit.css';
const PHONE = `screen and (max-width: ${MOBILE_BREAKPOINT}px)`;
const Container = createVar();
const ContainerHover = createVar();
const ContainerActive = createVar();
@ -28,14 +25,6 @@ export const Reaction = style([
selectors: {
'button&': {
cursor: 'pointer',
// Phone tap target: the chip is 26 px tall, so its hit area grows 9 px above and below (an ::after) and the
// same 9 px of margin opens the row, so the chips of one row and of the next never share a tap.
'@media': { [PHONE]: { position: 'relative', marginBlock: (PHONE_TAP - 26) / 2 } },
},
'button&::after': {
'@media': {
[PHONE]: { content: '""', position: 'absolute', inset: `${-(PHONE_TAP - 26) / 2}px 0` },
},
},
'&[aria-pressed=true]': {
vars: {

View file

@ -3,7 +3,6 @@ import React, { ReactNode, useCallback, useRef, useState } from 'react';
import { Badge, Chip, Icon, IconButton, Icons, ProgressBar, Spinner, Text, toRem } from 'folds';
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { Range } from 'react-range';
import { PhoneHitSpread24 } from '../../../styles/PhoneHit.css';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { IAudioInfo } from '../../../../types/matrix/common';
@ -160,7 +159,6 @@ export function AudioContent({
variant="SurfaceVariant"
size="300"
radii="Pill"
className={PhoneHitSpread24}
onClick={() => setMute(!mute)}
aria-pressed={mute}
>

View file

@ -299,10 +299,7 @@ export const BubbleLeftArrow = style({
});
// Phone tap target: a sender name is 23 px tall, so the button grows 11 px above and below (44+ px to tap) and the
// negative margin takes the growth back out of the layout. Sideways it cannot borrow from the layout the
// same way (a negative inline margin makes the button shrink inside its shrink-to-fit parent and clip the name), so
// a name under 44 px is 44 px wide, text kept at the start: real growth of at most 16 px, in a row with room for it.
// No background, border or outline moves.
// negative margin takes the growth back out of the layout. Hit area only: no background, border or outline moves.
const SCREEN_PHONE = `screen and (max-width: ${MOBILE_BREAKPOINT}px)`;
export const Username = style({
overflow: 'hidden',
@ -312,7 +309,7 @@ export const Username = style({
'button&': {
cursor: 'pointer',
'@media': {
[SCREEN_PHONE]: { paddingBlock: 11, marginBlock: -11, minWidth: 44, textAlign: 'start' },
[SCREEN_PHONE]: { paddingBlock: 11, marginBlock: -11 },
},
},
'button&:hover, button&:focus-visible': {

View file

@ -1,7 +1,6 @@
import React, { ComponentProps, forwardRef } from 'react';
import { Icon, IconButton, Input, config, Icons } from 'folds';
import { UseStateProvider } from '../UseStateProvider';
import { PhoneHit } from '../../styles/PhoneHit.css';
type PasswordInputProps = Omit<ComponentProps<typeof Input>, 'type' | 'size'> & {
size: '400' | '500';
@ -24,7 +23,6 @@ export const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
<>
{after}
<IconButton
className={PhoneHit}
onClick={() => setVisible(!visible)}
type="button"
variant={visible ? 'Warning' : variant}

View file

@ -4,7 +4,6 @@ import classNames from 'classnames';
import Linkify from 'linkify-react';
import * as css from './style.css';
import { LINKIFY_OPTS, scaleSystemEmoji } from '../../plugins/react-custom-html-parser';
import { PhoneHit } from '../../styles/PhoneHit.css';
export const RoomTopicViewer = as<
'div',
@ -27,7 +26,7 @@ export const RoomTopicViewer = as<
{name}
</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={requestClose} radii="300">
<IconButton size="300" onClick={requestClose} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -5,7 +5,6 @@ import { Box, Chip, Header, Icon, IconButton, Icons, Scroll, Text, as } from 'fo
import { ErrorBoundary } from 'react-error-boundary';
import * as css from './TextViewer.css';
import { copyToClipboard } from '../../utils/dom';
import { PhoneHit } from '../../styles/PhoneHit.css';
const ReactPrism = lazy(() => import('../../plugins/react-prism/ReactPrism'));
@ -54,7 +53,7 @@ export const TextViewer = as<'div', TextViewerProps>(
>
<Header className={css.TextViewerHeader} size="400">
<Box grow="Yes" alignItems="Center" gap="200">
<IconButton className={PhoneHit} size="300" radii="300" onClick={requestClose}>
<IconButton size="300" radii="300" onClick={requestClose}>
<Icon size="50" src={Icons.ArrowLeft} />
</IconButton>
<Text size="T300" truncate>

View file

@ -4,7 +4,6 @@ import { AuthType } from 'matrix-js-sdk';
import { StageComponentProps } from './types';
import { ErrorCode } from '../../cs-errorcode';
import { PasswordInput } from '../password-input';
import { PhoneHit } from '../../styles/PhoneHit.css';
export function PasswordStage({
stageData,
@ -46,7 +45,7 @@ export function PasswordStage({
<Box grow="Yes">
<Text size="H4">Account Password</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -1,7 +1,6 @@
import { Box, Button, color, config, Dialog, Header, Icon, IconButton, Icons, Text } from 'folds';
import React, { useCallback, useEffect, useState } from 'react';
import { StageComponentProps } from './types';
import { PhoneHit } from '../../styles/PhoneHit.css';
export function SSOStage({
ssoRedirectURL,
@ -57,7 +56,7 @@ export function SSOStage({
<Box grow="Yes">
<Text size="H4">SSO Login</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -6,7 +6,6 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
import { TUploadContent } from '../../utils/matrix';
import { bytesToSize, getFileTypeIcon } from '../../utils/common';
import { useMediaConfig } from '../../hooks/useMediaConfig';
import { PhoneHitSpread28 } from '../../styles/PhoneHit.css';
type CompactUploadCardRendererProps = {
isEncrypted?: boolean;
@ -64,7 +63,6 @@ export function CompactUploadCardRenderer({
</Chip>
)}
<IconButton
className={PhoneHitSpread28}
onClick={removeUpload}
aria-label="Cancel Upload"
variant="SurfaceVariant"

View file

@ -12,7 +12,6 @@ import {
} from '../../state/room/roomInputDrafts';
import { useObjectURL } from '../../hooks/useObjectURL';
import { useMediaConfig } from '../../hooks/useMediaConfig';
import { PhoneHitSpread28 } from '../../styles/PhoneHit.css';
type PreviewImageProps = {
fileItem: TUploadItem;
@ -161,7 +160,6 @@ export function UploadCardRenderer({
</Chip>
)}
<IconButton
className={PhoneHitSpread28}
onClick={removeUpload}
aria-label="Cancel Upload"
variant="SurfaceVariant"

View file

@ -1,4 +1,3 @@
import classNames from 'classnames';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { IPreviewUrlResponse } from 'matrix-js-sdk';
import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds';
@ -11,7 +10,6 @@ import {
useIntersectionObserver,
} from '../../hooks/useIntersectionObserver';
import * as css from './UrlPreviewCard.css';
import { PhoneHitInPlace } from '../../styles/PhoneHit.css';
import { tryDecodeURIComponent } from '../../utils/dom';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
@ -183,10 +181,7 @@ export const UrlPreviewHolder = as<'div'>(({ children, ...props }, ref) => {
<>
<div className={css.UrlPreviewHolderGradient({ position: 'Left' })} />
<IconButton
className={classNames(
css.UrlPreviewHolderBtn({ position: 'Left' }),
PhoneHitInPlace
)}
className={css.UrlPreviewHolderBtn({ position: 'Left' })}
variant="Secondary"
radii="Pill"
size="300"
@ -204,10 +199,7 @@ export const UrlPreviewHolder = as<'div'>(({ children, ...props }, ref) => {
<>
<div className={css.UrlPreviewHolderGradient({ position: 'Right' })} />
<IconButton
className={classNames(
css.UrlPreviewHolderBtn({ position: 'Right' }),
PhoneHitInPlace
)}
className={css.UrlPreviewHolderBtn({ position: 'Right' })}
variant="Primary"
radii="Pill"
size="300"

View file

@ -43,7 +43,6 @@ import { getPowerTagIconSrc, useGetMemberPowerTag } from '../../hooks/useMemberP
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
import { PhoneHit } from '../../styles/PhoneHit.css';
type SelfDemoteAlertProps = {
power: number;
@ -71,7 +70,7 @@ function SelfDemoteAlert({ power, onCancel, onChange }: SelfDemoteAlertProps) {
<Box grow="Yes">
<Text size="H4">Self Demotion</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@ -121,7 +120,7 @@ function SharedPowerAlert({ power, onCancel, onChange }: SharedPowerAlertProps)
<Box grow="Yes">
<Text size="H4">Shared Power</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -54,7 +54,6 @@ import { StateEvent } from '../../../types/matrix/room';
import { getViaServers } from '../../plugins/via-servers';
import { rateLimitedActions } from '../../utils/matrix';
import { useAlive } from '../../hooks/useAlive';
import { PhoneHit } from '../../styles/PhoneHit.css';
const SEARCH_OPTS: UseAsyncSearchOptions = {
limit: 500,
@ -202,7 +201,7 @@ export function AddExistingModal({ parentId, space, requestClose }: AddExistingM
<Text size="H4">Add Existing</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} size="300" radii="300" onClick={requestClose}>
<IconButton size="300" radii="300" onClick={requestClose}>
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -5,7 +5,6 @@ import { StatusDivider } from './components';
import { CallEmbed, useCallControlState } from '../../plugins/call';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { callEmbedAtom } from '../../state/callEmbed';
import { PhoneHitSpread26 } from '../../styles/PhoneHit.css';
type MicrophoneButtonProps = {
enabled: boolean;
@ -27,7 +26,6 @@ function MicrophoneButton({ enabled, onToggle, disabled }: MicrophoneButtonProps
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread26}
ref={anchorRef}
variant={enabled ? 'Surface' : 'Warning'}
fill="Soft"
@ -61,7 +59,6 @@ function SoundButton({ enabled, onToggle, disabled }: SoundButtonProps) {
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread26}
ref={anchorRef}
variant={enabled ? 'Surface' : 'Warning'}
fill="Soft"
@ -102,7 +99,6 @@ function VideoButton({ enabled, onToggle, disabled }: VideoButtonProps) {
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread26}
ref={anchorRef}
variant={enabled ? 'Success' : 'Surface'}
fill="Soft"
@ -140,7 +136,6 @@ function ScreenShareButton({ enabled, onToggle, disabled }: ScreenShareButtonPro
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread26}
ref={anchorRef}
variant={enabled ? 'Success' : 'Surface'}
fill="Soft"

View file

@ -25,7 +25,6 @@ import {
SoundButton,
VideoButton,
} from './Controls';
import { PhoneHitSpread40 } from '../../styles/PhoneHit.css';
import { CallEmbed, useCallControlState } from '../../plugins/call';
import { useResizeObserver } from '../../hooks/useResizeObserver';
import { stopPropagation } from '../../utils/keyboard';
@ -170,7 +169,6 @@ export function CallControls({ callEmbed }: CallControlsProps) {
}
>
<IconButton
className={PhoneHitSpread40}
variant="Surface"
fill="Soft"
radii="400"

View file

@ -4,7 +4,6 @@ import { useAtom } from 'jotai';
import * as css from './styles.css';
import { callChatAtom } from '../../state/callEmbed';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { PhoneHitSpread40 } from '../../styles/PhoneHit.css';
export function ControlDivider() {
return (
@ -32,7 +31,6 @@ export function MicrophoneButton({ enabled, onToggle }: MicrophoneButtonProps) {
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread40}
ref={anchorRef}
variant={enabled ? 'Surface' : 'Warning'}
fill="Soft"
@ -66,7 +64,6 @@ export function SoundButton({ enabled, onToggle }: SoundButtonProps) {
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread40}
ref={anchorRef}
variant={enabled ? 'Surface' : 'Warning'}
fill="Soft"
@ -106,7 +103,6 @@ export function VideoButton({ enabled, onToggle }: VideoButtonProps) {
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread40}
ref={anchorRef}
variant={enabled ? 'Success' : 'Surface'}
fill="Soft"
@ -144,7 +140,6 @@ export function ScreenShareButton({ enabled, onToggle }: ScreenShareButtonProps)
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread40}
ref={anchorRef}
variant={enabled ? 'Success' : 'Surface'}
fill="Soft"
@ -175,7 +170,6 @@ export function ChatButton() {
>
{(anchorRef) => (
<IconButton
className={PhoneHitSpread40}
ref={anchorRef}
variant={chat ? 'Success' : 'Surface'}
fill="Soft"

View file

@ -30,7 +30,6 @@ import {
AccountDataSubmitCallback,
} from '../../../components/AccountDataEditor';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type DeveloperToolsProps = {
requestClose: () => void;
@ -93,7 +92,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -22,7 +22,6 @@ import { useTextAreaCodeEditor } from '../../../hooks/useTextAreaCodeEditor';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { syntaxErrorPosition } from '../../../utils/dom';
import { Cursor } from '../../../plugins/text-area';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const EDITOR_INTENT_SPACE_COUNT = 2;
@ -119,7 +118,7 @@ export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventPro
</Chip>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -31,7 +31,6 @@ import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useTextAreaCodeEditor } from '../../../hooks/useTextAreaCodeEditor';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const EDITOR_INTENT_SPACE_COUNT = 2;
@ -276,7 +275,7 @@ export function StateEventEditor({ type, stateKey, requestClose }: StateEventEdi
</Chip>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -4,7 +4,6 @@ import { Page, PageContent, PageHeader } from '../../../components/page';
import { ImagePack } from '../../../plugins/custom-emoji';
import { ImagePackView } from '../../../components/image-pack-view';
import { RoomPacks } from './RoomPacks';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type EmojisStickersProps = {
requestClose: () => void;
@ -30,7 +29,7 @@ export function EmojisStickers({ requestClose }: EmojisStickersProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -40,7 +40,6 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useAlive } from '../../../hooks/useAlive';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type CreatePackTileProps = {
packs: ImagePack[];
@ -216,7 +215,6 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
{canEdit &&
(removed ? (
<IconButton
className={PhoneHit}
size="300"
radii="Pill"
variant="Critical"
@ -227,7 +225,6 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
</IconButton>
) : (
<IconButton
className={PhoneHit}
size="300"
radii="Pill"
variant="Secondary"

View file

@ -28,7 +28,6 @@ import { useRoom } from '../../../hooks/useRoom';
import { useStateEvent } from '../../../hooks/useStateEvent';
import { stopPropagation } from '../../../utils/keyboard';
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const ROOM_ENC_ALGO = 'm.megolm.v1.aes-sha2';
@ -124,12 +123,7 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
<Box grow="Yes">
<Text size="H4">Enable Encryption</Text>
</Box>
<IconButton
className={PhoneHit}
size="300"
onClick={() => setPrompt(false)}
radii="300"
>
<IconButton size="300" onClick={() => setPrompt(false)} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -39,7 +39,6 @@ import { useAlive } from '../../../hooks/useAlive';
import { creatorsSupported } from '../../../utils/matrix';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { BreakWord } from '../../../styles/Text.css';
import { PhoneHit } from '../../../styles/PhoneHit.css';
function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
const mx = useMatrixClient();
@ -106,7 +105,7 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
<Box grow="Yes">
<Text size="H4">{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={requestClose} radii="300">
<IconButton size="300" onClick={requestClose} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -56,7 +56,6 @@ import { useSpaceOptionally } from '../../../hooks/useSpace';
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../../hooks/useMemberPowerTag';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { getMouseEventCords } from '../../../utils/dom';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
limit: 1000,
@ -162,7 +161,7 @@ export function Members({ requestClose }: MembersProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>
@ -278,7 +277,6 @@ export function Members({ requestClose }: MembersProps) {
anchorRef={scrollTopAnchorRef}
>
<IconButton
className={PhoneHit}
onClick={() => virtualizer.scrollToOffset(0)}
variant="Surface"
radii="Pill"

View file

@ -49,7 +49,6 @@ import { useAlive } from '../../../hooks/useAlive';
import { BetaNoticeBadge } from '../../../components/BetaNoticeBadge';
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
import { creatorsSupported } from '../../../utils/matrix';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type EditPowerProps = {
maxPower: number;
@ -370,7 +369,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
</Chip>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -24,7 +24,6 @@ import {
import { CreateRoomModalState } from '../../state/createRoomModal';
import { stopPropagation } from '../../utils/keyboard';
import { CreateRoomType } from '../../components/create-room/types';
import { PhoneHit } from '../../styles/PhoneHit.css';
type CreateRoomModalProps = {
state: CreateRoomModalState;
@ -64,7 +63,7 @@ function CreateRoomModal({ state }: CreateRoomModalProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} size="300" radii="300" onClick={closeDialog}>
<IconButton size="300" radii="300" onClick={closeDialog}>
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -23,7 +23,6 @@ import {
} from '../../state/hooks/createSpaceModal';
import { CreateSpaceModalState } from '../../state/createSpaceModal';
import { stopPropagation } from '../../utils/keyboard';
import { PhoneHit } from '../../styles/PhoneHit.css';
type CreateSpaceModalProps = {
state: CreateSpaceModalState;
@ -62,7 +61,7 @@ function CreateSpaceModal({ state }: CreateSpaceModalProps) {
<Text size="H4">New Space</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} size="300" radii="300" onClick={closeDialog}>
<IconButton size="300" radii="300" onClick={closeDialog}>
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -1,7 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { PhoneHit } from '../../styles/PhoneHit.css';
import { Page, PageContent, PageContentCenter, PageHeader } from '../../components/page';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { getHomePath } from '../../pages/pathUtils';
@ -34,11 +33,7 @@ export function Desk() {
<Box grow="Yes" alignItems="Center" gap="200">
<Box grow="Yes" basis="No">
{phone && (
<IconButton
className={PhoneHit}
aria-label="Back to rooms"
onClick={() => navigate(getHomePath())}
>
<IconButton aria-label="Back to rooms" onClick={() => navigate(getHomePath())}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -10,7 +10,6 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
import { allRoomsAtom } from '../../state/room-list/roomList';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { BackRouteHandler } from '../../components/BackRouteHandler';
import { PhoneHit } from '../../styles/PhoneHit.css';
type JoinBeforeNavigateProps = { roomIdOrAlias: string; eventId?: string; viaServers?: string[] };
export function JoinBeforeNavigate({
@ -39,7 +38,7 @@ export function JoinBeforeNavigate({
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton className={PhoneHit} onClick={onBack}>
<IconButton onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -30,7 +30,6 @@ import { IPowerLevels } from '../../hooks/usePowerLevels';
import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators';
import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../components/invite-user-prompt';
import { PhoneHitSpread24 } from '../../styles/PhoneHit.css';
type HierarchyItemWithParent = HierarchyItem & {
parentId: string;
@ -232,7 +231,6 @@ export function HierarchyItemMenu({
return (
<Box gap="200" alignItems="Center" shrink="No">
<IconButton
className={PhoneHitSpread24}
onClick={handleOpenMenu}
size="300"
variant="SurfaceVariant"

View file

@ -48,7 +48,6 @@ import { useGetRoom } from '../../hooks/useGetRoom';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions';
import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators';
import { PhoneHit } from '../../styles/PhoneHit.css';
const useCanDropLobbyItem = (
space: Room,
@ -414,7 +413,6 @@ export function Lobby() {
onVisibilityChange={setOnTop}
>
<IconButton
className={PhoneHit}
onClick={() => virtualizer.scrollToOffset(0)}
variant="SurfaceVariant"
radii="Pill"

View file

@ -38,7 +38,6 @@ import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../components/invite-user-prompt';
import { PhoneHit } from '../../styles/PhoneHit.css';
type LobbyMenuProps = {
powerLevels: IPowerLevels;
@ -166,7 +165,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
<Box shrink="No">
<BackRouteHandler>
{(onBack) => (
<IconButton className={PhoneHit} fill="None" onClick={onBack}>
<IconButton fill="None" onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}
@ -241,7 +240,6 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
>
{(triggerRef) => (
<IconButton
className={PhoneHit}
fill="None"
onClick={handleOpenMenu}
ref={triggerRef}

View file

@ -23,7 +23,6 @@ import { SearchResultGroup } from './SearchResultGroup';
import { SearchInput } from './SearchInput';
import { SearchFilters } from './SearchFilters';
import { VirtualTile } from '../../components/virtualizer';
import { PhoneHit } from '../../styles/PhoneHit.css';
const useSearchPathSearchParams = (searchParams: URLSearchParams): _SearchPathSearchParams =>
useMemo(
@ -194,7 +193,6 @@ export function MessageSearch({
<Box direction="Column" gap="700">
<ScrollTopContainer scrollRef={scrollRef} anchorRef={scrollTopAnchorRef}>
<IconButton
className={PhoneHit}
onClick={() => virtualizer.scrollToOffset(0)}
variant="SurfaceVariant"
radii="Pill"

View file

@ -66,7 +66,6 @@ import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
import { livekitSupport } from '../../hooks/useLivekitSupport';
import { StateEvent } from '../../../types/matrix/room';
import { webRTCSupported } from '../../utils/rtc';
import { PhoneHitSpread24 } from '../../styles/PhoneHit.css';
type RoomNavItemMenuProps = {
room: Room;
@ -230,7 +229,6 @@ function CallChatToggle() {
return (
<IconButton
className={PhoneHitSpread24}
onClick={() => setChat(!chat)}
aria-pressed={chat}
aria-label="Toggle Chat"
@ -438,7 +436,6 @@ export function RoomNavItem({
}
>
<IconButton
className={PhoneHitSpread24}
onClick={handleOpenMenu}
aria-pressed={!!menuAnchor}
aria-controls={`menu-${room.roomId}`}

View file

@ -17,7 +17,6 @@ import { Permissions } from './permissions';
import { RoomSettingsPage } from '../../state/roomSettings';
import { useRoom } from '../../hooks/useRoom';
import { DeveloperTools } from '../common-settings/developer-tools';
import { PhoneHit } from '../../styles/PhoneHit.css';
type RoomSettingsMenuItem = {
page: RoomSettingsPage;
@ -118,7 +117,7 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
</Box>
<Box shrink="No">
{screenSize === ScreenSize.Mobile && (
<IconButton className={PhoneHit} onClick={requestClose} variant="Background">
<IconButton onClick={requestClose} variant="Background">
<Icon src={Icons.Cross} />
</IconButton>
)}

View file

@ -15,7 +15,6 @@ import {
} from '../../common-settings/general';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type GeneralProps = {
requestClose: () => void;
@ -36,7 +35,7 @@ export function General({ requestClose }: GeneralProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -9,7 +9,6 @@ import { usePermissionGroups } from './usePermissionItems';
import { PermissionGroups, Powers, PowersEditor } from '../../common-settings/permissions';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type PermissionsProps = {
requestClose: () => void;
@ -46,7 +45,7 @@ export function Permissions({ requestClose }: PermissionsProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -6,7 +6,6 @@ import { Page, PageHeader } from '../../components/page';
import { callChatAtom } from '../../state/callEmbed';
import { RoomView } from './RoomView';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { PhoneHit } from '../../styles/PhoneHit.css';
export function CallChatView() {
const { eventId } = useParams();
@ -42,12 +41,7 @@ export function CallChatView() {
}
>
{(triggerRef) => (
<IconButton
className={PhoneHit}
ref={triggerRef}
variant="Surface"
onClick={handleClose}
>
<IconButton ref={triggerRef} variant="Surface" onClick={handleClose}>
<Icon src={Icons.Cross} />
</IconButton>
)}

View file

@ -59,7 +59,6 @@ import { useSpaceOptionally } from '../../hooks/useSpace';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { PhoneHit } from '../../styles/PhoneHit.css';
type MemberDrawerHeaderProps = {
room: Room;
@ -88,7 +87,6 @@ function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) {
>
{(triggerRef) => (
<IconButton
className={PhoneHit}
ref={triggerRef}
variant="Background"
onClick={() => setPeopleDrawer(false)}
@ -361,7 +359,6 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
<ScrollTopContainer scrollRef={scrollRef} anchorRef={scrollTopAnchorRef}>
<IconButton
className={PhoneHit}
onClick={() => virtualizer.scrollToOffset(0)}
variant="Surface"
radii="Pill"

View file

@ -28,8 +28,7 @@ import {
config,
toRem,
} from 'folds';
import { RoomOutbox } from './RoomOutbox';
import { sendTracked } from '../../state/room/outboxSend';
import { RoomOutbox, sendTracked } from './RoomOutbox';
import { clearDraft, loadDraft, saveDraft } from '../../state/room/composerStore';
import { useMatrixClient } from '../../hooks/useMatrixClient';
@ -116,7 +115,6 @@ import { useTheme } from '../../hooks/useTheme';
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
import { useComposingCheck } from '../../hooks/useComposingCheck';
import { PhoneHitSpread24, PhoneHitSpread32 } from '../../styles/PhoneHit.css';
interface RoomInputProps {
editor: Editor;
@ -335,7 +333,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
});
handleCancelUpload(uploads);
const contents = fulfilledPromiseSettledResult(await Promise.allSettled(contentsPromises));
contents.forEach((content) => sendTracked(mx, roomId, content as Record<string, unknown>));
contents.forEach((content) => mx.sendMessage(roomId, content as any));
};
const submit = useCallback(() => {
@ -494,7 +492,11 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
await getImageUrlBlob(stickerUrl)
);
sendTracked(mx, roomId, { body: label, url: mxc, info }, EventType.Sticker);
mx.sendEvent(roomId, EventType.Sticker, {
body: label,
url: mxc,
info,
});
};
return (
@ -606,7 +608,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
style={{ padding: `${config.space.S200} ${config.space.S300} 0` }}
>
<IconButton
className={PhoneHitSpread24}
onClick={() => setReplyDraft(undefined)}
variant="SurfaceVariant"
size="300"
@ -640,7 +641,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
}
before={
<IconButton
className={PhoneHitSpread32}
onClick={() => pickFile('*')}
variant="SurfaceVariant"
size="300"
@ -652,7 +652,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
after={
<>
<IconButton
className={PhoneHitSpread32}
variant="SurfaceVariant"
size="300"
radii="300"
@ -695,7 +694,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
>
{!hideStickerBtn && (
<IconButton
className={PhoneHitSpread32}
aria-pressed={emojiBoardTab === EmojiBoardTab.Sticker}
onClick={() => setEmojiBoardTab(EmojiBoardTab.Sticker)}
variant="SurfaceVariant"
@ -709,7 +707,6 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
</IconButton>
)}
<IconButton
className={PhoneHitSpread32}
ref={emojiBtnRef}
aria-pressed={
hideStickerBtn ? !!emojiBoardTab : emojiBoardTab === EmojiBoardTab.Emoji
@ -729,13 +726,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
</PopOut>
)}
</UseStateProvider>
<IconButton
className={PhoneHitSpread32}
onClick={submit}
variant="SurfaceVariant"
size="300"
radii="300"
>
<IconButton onClick={submit} variant="SurfaceVariant" size="300" radii="300">
<Icon src={Icons.Send} />
</IconButton>
</>

View file

@ -1,102 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { enqueueOutbox, failOutbox, loadOutbox } from '../../state/room/composerStore';
import { RoomOutbox } from './RoomOutbox';
// The component's hooks are held still (state is its initial value, effects do not run) so the element tree
// it returns can be read and its click handlers called without a DOM.
vi.mock('react', async (importOriginal) => {
const actual = await importOriginal<typeof import('react')>();
const useState = (init: unknown) => [
typeof init === 'function' ? (init as () => unknown)() : init,
() => undefined,
];
return {
...actual,
default: { ...actual, useState, useEffect: () => undefined },
useState,
useEffect: () => undefined,
};
});
type El = { props?: { children?: unknown; onClick?: () => void } };
const flatten = (node: unknown): El[] => {
if (Array.isArray(node)) return node.flatMap(flatten);
if (node && typeof node === 'object') {
const el = node as El;
return [el, ...flatten(el.props?.children)];
}
return [];
};
const textOf = (node: unknown): string => {
if (typeof node === 'string' || typeof node === 'number') return String(node);
if (Array.isArray(node)) return node.map(textOf).join('');
if (node && typeof node === 'object') return textOf((node as El).props?.children);
return '';
};
/** The onClick of the chip whose label is `label`. */
const clickOf = (tree: unknown, label: string) => {
const chip = flatten(tree).find((e) => e.props?.onClick && textOf(e) === label);
if (!chip?.props?.onClick) throw new Error(`no ${label} chip`);
return chip.props.onClick;
};
describe('RoomOutbox actions', () => {
beforeEach(() => {
const m = new Map<string, string>();
vi.stubGlobal('localStorage', {
getItem: (k: string) => m.get(k) ?? null,
setItem: (k: string, v: string) => {
m.set(k, v);
},
removeItem: (k: string) => {
m.delete(k);
},
});
enqueueOutbox('@a:x', { txnId: 'keep', roomId: '!r:x', content: { body: 'stay' } });
enqueueOutbox('@a:x', { txnId: 'bad', roomId: '!r:x', content: { body: 'did not go' } });
failOutbox('@a:x', 'bad', 'ECONNRESET');
});
afterEach(() => {
vi.unstubAllGlobals();
});
const mount = () => {
const mx = {
getUserId: () => '@a:x',
makeTxnId: () => 'txn-new',
getRoom: () => undefined,
sendEvent: vi.fn().mockResolvedValue({ event_id: '$e' }),
};
const tree = (RoomOutbox as unknown as (p: unknown) => unknown)({ mx, roomId: '!r:x' });
return { mx, tree };
};
it('Discard removes that message from the outbox and no other', () => {
const { mx, tree } = mount();
// both entries are shown; the failed one is the one with Discard
clickOf(tree, 'Discard')();
expect(loadOutbox('@a:x').map((e) => e.txnId)).toEqual(['keep']);
expect(mx.sendEvent).not.toHaveBeenCalled();
});
it('Resend sends that message again and does not discard it first', async () => {
const { mx, tree } = mount();
// the server has not answered yet: the resend is in flight
mx.sendEvent.mockReturnValue(
new Promise<never>(() => {
// never settles
})
);
clickOf(tree, 'Resend')();
await Promise.resolve();
expect(mx.sendEvent).toHaveBeenCalledTimes(1);
expect(mx.sendEvent).toHaveBeenCalledWith(
'!r:x',
'm.room.message',
{ body: 'did not go' },
'bad'
);
// still in the outbox, pending again under the same id, until the server confirms it
expect(loadOutbox('@a:x').find((e) => e.txnId === 'bad')).toMatchObject({ status: 'pending' });
});
});

View file

@ -1,64 +0,0 @@
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { enqueueOutbox, failOutbox } from '../../state/room/composerStore';
import { RoomOutbox } from './RoomOutbox';
// What the person sees under the timeline for sends the server has not confirmed.
const text = (html: string) => html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ');
describe('RoomOutbox as rendered', () => {
beforeEach(() => {
const m = new Map<string, string>();
vi.stubGlobal('localStorage', {
getItem: (k: string) => m.get(k) ?? null,
setItem: (k: string, v: string) => {
m.set(k, v);
},
removeItem: (k: string) => {
m.delete(k);
},
});
});
afterEach(() => {
vi.unstubAllGlobals();
});
const mx = { getUserId: () => '@a:x' } as never;
const entry = (txnId: string, roomId: string, body: string) => ({
txnId,
roomId,
content: { body },
});
it('renders nothing when the room has no unconfirmed sends', () => {
enqueueOutbox('@a:x', entry('t1', '!other:x', 'elsewhere'));
expect(renderToStaticMarkup(<RoomOutbox mx={mx} roomId="!r:x" />)).toBe('');
});
it('shows a fresh send as Sending with its text and no Resend', () => {
enqueueOutbox('@a:x', entry('t1', '!r:x', 'on its way'), Date.now() + 1000);
const html = renderToStaticMarkup(<RoomOutbox mx={mx} roomId="!r:x" />);
expect(html).toContain('data-outbox-status="pending"');
expect(text(html)).toContain('Sending…');
expect(text(html)).toContain('on its way');
expect(text(html)).not.toContain('Resend');
});
it('shows a failed send with its error, Resend and Discard', () => {
enqueueOutbox('@a:x', entry('t2', '!r:x', 'did not go'));
failOutbox('@a:x', 't2', 'ECONNRESET');
const html = renderToStaticMarkup(<RoomOutbox mx={mx} roomId="!r:x" />);
expect(html).toContain('data-outbox-status="failed"');
expect(text(html)).toContain('Not sent: ECONNRESET');
expect(text(html)).toContain('Resend');
expect(text(html)).toContain('Discard');
});
it('shows a send left over from before this page load as interrupted, resendable', () => {
enqueueOutbox('@a:x', entry('t3', '!r:x', 'lost to a reload'), 5);
const html = renderToStaticMarkup(<RoomOutbox mx={mx} roomId="!r:x" />);
expect(text(html)).toContain('Interrupted by a reload');
expect(text(html)).toContain('Resend');
});
});

View file

@ -1,28 +1,57 @@
import { describe, expect, it } from 'vitest';
import { SLOW_SEND_MS, outboxView } from './RoomOutbox';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { loadOutbox } from '../../state/room/composerStore';
import { sendTracked } from './RoomOutbox';
describe('outboxView', () => {
it('walks sending -> still sending (resendable) -> failed', () => {
expect(outboxView({ status: 'pending', ts: 100 }, 100, 0)).toMatchObject({
label: 'Sending…',
canResend: false,
});
expect(outboxView({ status: 'pending', ts: 100 }, 100 + SLOW_SEND_MS, 0)).toMatchObject({
label: expect.stringMatching(/^Still sending/),
canResend: true,
});
expect(outboxView({ status: 'failed', ts: 100, error: 'x' }, 100, 0)).toMatchObject({
failed: true,
canResend: true,
});
const stubLocalStorage = () => {
const m = new Map<string, string>();
vi.stubGlobal('localStorage', {
getItem: (k: string) => m.get(k) ?? null,
setItem: (k: string, v: string) => {
m.set(k, v);
},
removeItem: (k: string) => {
m.delete(k);
},
});
};
const fakeClient = (send: (...a: unknown[]) => Promise<unknown>) =>
({ getUserId: () => '@a:x', makeTxnId: () => 'txn-1', sendMessage: send } as never);
describe('sendTracked', () => {
beforeEach(stubLocalStorage);
afterEach(() => {
vi.unstubAllGlobals();
});
it('offers Resend to a pending send at 30 s and not a millisecond before', () => {
const at = (ms: number) => outboxView({ status: 'pending', ts: 100 }, 100 + ms, 0).canResend;
expect([at(29_999), at(30_000)]).toEqual([false, true]);
const content = { msgtype: 'm.text', body: 'typed before reload' };
it('holds the message in the outbox while the send is in flight, so a reload keeps it', async () => {
let finish!: () => void;
const mx = fakeClient(
() =>
new Promise((r) => {
finish = () => r({ event_id: '$e' });
})
);
const done = sendTracked(mx, '!r:x', content);
expect(loadOutbox('@a:x')).toMatchObject([{ txnId: 'txn-1', status: 'pending', content }]);
finish();
await done;
expect(loadOutbox('@a:x')).toEqual([]);
});
it('an entry older than this page load reads as interrupted', () => {
expect(outboxView({ status: 'pending', ts: 5 }, 6, 10).label).toMatch(/Interrupted/);
it('a failed send stays, marked failed, and is not cleared', async () => {
const mx = fakeClient(() => Promise.reject(new Error('ECONNRESET')));
await sendTracked(mx, '!r:x', content);
expect(loadOutbox('@a:x')).toMatchObject([
{ txnId: 'txn-1', status: 'failed', error: 'ECONNRESET', content },
]);
});
it('sends under the stored txn id, so a resend after an unknown outcome deduplicates', async () => {
const send = vi.fn().mockResolvedValue({});
await sendTracked(fakeClient(send), '!r:x', content);
expect(send).toHaveBeenCalledWith('!r:x', content, 'txn-1');
});
});

View file

@ -3,44 +3,32 @@ import { MatrixClient } from 'matrix-js-sdk';
import { Box, Chip, Text, color, config } from 'folds';
import {
OutboxEntry,
enqueueOutbox,
failOutbox,
loadOutbox,
removeOutbox,
retryOutbox,
subscribeOutbox,
} from '../../state/room/composerStore';
import { PAGE_START, resendTracked } from '../../state/room/outboxSend';
/** A send still unconfirmed after this long is offered Resend: the request may be hung. */
export const SLOW_SEND_MS = 30_000;
const attempt = (mx: MatrixClient, userId: string, entry: OutboxEntry): Promise<void> =>
mx
.sendMessage(entry.roomId, entry.content as never, entry.txnId)
.then(() => removeOutbox(userId, entry.txnId))
.catch((e: unknown) =>
failOutbox(userId, entry.txnId, e instanceof Error ? e.message : String(e))
);
export type OutboxView = { label: string; failed: boolean; canResend: boolean };
export const outboxView = (
entry: Pick<OutboxEntry, 'status' | 'ts' | 'error'>,
now: number,
since: number = PAGE_START
): OutboxView => {
if (entry.status === 'failed') {
return {
label: `Not sent${entry.error ? `: ${entry.error}` : ''}`,
failed: true,
canResend: true,
};
}
if (entry.ts < since) {
return {
label: 'Interrupted by a reload: may not have been sent',
failed: false,
canResend: true,
};
}
if (now - entry.ts >= SLOW_SEND_MS) {
return {
label: 'Still sending: slow connection (Resend may duplicate it if it lands later)',
failed: false,
canResend: true,
};
}
return { label: 'Sending…', failed: false, canResend: false };
/** Send a message, keeping it in the durable outbox until the server confirms it. */
export const sendTracked = (
mx: MatrixClient,
roomId: string,
content: Record<string, unknown>
): Promise<void> => {
const userId = mx.getUserId() ?? '';
const entry = { roomId, content, txnId: mx.makeTxnId() };
enqueueOutbox(userId, entry);
return attempt(mx, userId, { ...entry, status: 'pending', ts: Date.now() });
};
export const useRoomOutbox = (userId: string, roomId: string): OutboxEntry[] => {
@ -54,28 +42,19 @@ export const useRoomOutbox = (userId: string, roomId: string): OutboxEntry[] =>
return entries;
};
/** Re-render every few seconds while anything is waiting, so a slow send turns resendable. */
const useNow = (active: boolean): number => {
const [now, setNow] = useState(Date.now());
useEffect(() => {
if (!active) return undefined;
setNow(Date.now());
const id = setInterval(() => setNow(Date.now()), 5000);
return () => clearInterval(id);
}, [active]);
return now;
};
/** Entries still 'pending' that predate this page load lost their promise in a reload: outcome unknown. */
const pageStart = Date.now();
type RoomOutboxProps = { mx: MatrixClient; roomId: string };
/**
* Messages this client sent that the server has not confirmed: sending, slow, interrupted by a
* reload, or failed, each with Resend where one makes sense and Discard.
* Messages this client sent that the server has not confirmed: still sending, or failed.
* They also appear here after a reload, since the outbox is durable. Resending reuses the
* original txn id, so a send that did land the first time is not duplicated.
*/
export function RoomOutbox({ mx, roomId }: RoomOutboxProps) {
const userId = mx.getUserId() ?? '';
const entries = useRoomOutbox(userId, roomId);
const now = useNow(entries.length > 0);
if (entries.length === 0) return null;
return (
@ -85,32 +64,40 @@ export function RoomOutbox({ mx, roomId }: RoomOutboxProps) {
style={{ padding: `${config.space.S200} ${config.space.S300} 0` }}
>
{entries.map((entry) => {
const view = outboxView(entry, now);
const failed = entry.status === 'failed';
const interrupted = !failed && entry.ts < pageStart;
const canResend = failed || interrupted;
return (
<Box key={entry.txnId} alignItems="Center" gap="200" data-outbox-status={entry.status}>
<Box grow="Yes" direction="Column">
<Text
size="T200"
style={{ color: view.failed ? color.Critical.Main : color.Surface.OnContainer }}
style={{ color: failed ? color.Critical.Main : color.Surface.OnContainer }}
>
{view.label}
{(() => {
if (failed) return `Not sent${entry.error ? `: ${entry.error}` : ''}`;
return interrupted ? 'Interrupted by a reload: may not have been sent' : 'Sending…';
})()}
</Text>
<Text size="T300" truncate>
{String(entry.content.body ?? '')}
</Text>
</Box>
{view.canResend && (
{canResend && (
<>
<Chip variant="Surface" radii="Pill" onClick={() => resendTracked(mx, entry)}>
<Text size="B300">Resend</Text>
</Chip>
<Chip
variant="Surface"
radii="Pill"
onClick={() => removeOutbox(userId, entry.txnId)}
>
<Text size="B300">Discard</Text>
</Chip>
<Chip
variant="Surface"
radii="Pill"
onClick={() => {
retryOutbox(userId, entry.txnId);
attempt(mx, userId, entry);
}}
>
<Text size="B300">Resend</Text>
</Chip>
<Chip variant="Surface" radii="Pill" onClick={() => removeOutbox(userId, entry.txnId)}>
<Text size="B300">Discard</Text>
</Chip>
</>
)}
</Box>

View file

@ -47,7 +47,6 @@ import {
import { isKeyHotkey } from 'is-hotkey';
import { Opts as LinkifyOpts } from 'linkifyjs';
import { useTranslation } from 'react-i18next';
import { isUnconfirmedLocalEcho } from './localEcho';
import { eventWithShortcode, factoryEventSentBy, getMxIdLocalPart } from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
@ -390,19 +389,6 @@ const useLiveEventArrive = (room: Room, onArrive: (mEvent: MatrixEvent) => void)
}, [room, onArrive]);
};
/** A local echo changes status (or becomes its remote echo) without a Timeline event: redraw. */
const useLocalEchoRefresh = (room: Room, onUpdate: () => void) => {
useEffect(() => {
const handle: RoomEventHandlerMap[RoomEvent.LocalEchoUpdated] = (_evt, r) => {
if (r.roomId === room.roomId) onUpdate();
};
room.on(RoomEvent.LocalEchoUpdated, handle);
return () => {
room.removeListener(RoomEvent.LocalEchoUpdated, handle);
};
}, [room, onUpdate]);
};
const useLiveTimelineRefresh = (room: Room, onRefresh: () => void) => {
useEffect(() => {
const handleTimelineRefresh: RoomEventHandlerMap[RoomEvent.TimelineRefresh] = (r) => {
@ -662,18 +648,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
)
);
useLocalEchoRefresh(
room,
useCallback(() => {
// the echo was hidden while unconfirmed, so its arrival scrolled to nothing: scroll now
if (atBottomRef.current) {
scrollToBottomRef.current.count += 1;
scrollToBottomRef.current.smooth = true;
}
setTimeline((ct) => ({ ...ct }));
}, [])
);
const handleOpenEvent = useCallback(
async (
evtId: string,
@ -1672,7 +1646,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
if (mEvent.isRedacted() && !showHiddenEvents) {
return null;
}
if (isUnconfirmedLocalEcho(mEvent)) return null;
if (!newDivider && readUptoEventIdRef.current) {
newDivider = prevEvent?.getId() === readUptoEventIdRef.current;

View file

@ -72,7 +72,6 @@ import { RoomSettingsPage } from '../../state/roomSettings';
import { useCallEmbed, useCallStart } from '../../hooks/useCallEmbed';
import { useLivekitSupport } from '../../hooks/useLivekitSupport';
import { webRTCSupported } from '../../utils/rtc';
import { PhoneHitSpread40 } from '../../styles/PhoneHit.css';
type RoomMenuProps = {
room: Room;
@ -337,7 +336,6 @@ function CallButton() {
>
{(triggerRef) => (
<IconButton
className={PhoneHitSpread40}
variant="Surface"
fill="None"
ref={triggerRef}
@ -457,7 +455,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
<BackRouteHandler>
{(onBack) => (
<Box shrink="No" alignItems="Center">
<IconButton className={PhoneHitSpread40} fill="None" onClick={onBack}>
<IconButton fill="None" onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
</Box>
@ -533,12 +531,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
}
>
{(triggerRef) => (
<IconButton
className={PhoneHitSpread40}
fill="None"
ref={triggerRef}
onClick={handleSearchClick}
>
<IconButton fill="None" ref={triggerRef} onClick={handleSearchClick}>
<Icon size="400" src={Icons.Search} />
</IconButton>
)}
@ -555,7 +548,6 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
>
{(triggerRef) => (
<IconButton
className={PhoneHitSpread40}
fill="None"
style={{ position: 'relative' }}
onClick={handleOpenPinMenu}
@ -620,12 +612,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
}
>
{(triggerRef) => (
<IconButton
className={PhoneHitSpread40}
fill="None"
ref={triggerRef}
onClick={handleMemberToggle}
>
<IconButton fill="None" ref={triggerRef} onClick={handleMemberToggle}>
<Icon size="400" src={Icons.User} />
</IconButton>
)}
@ -644,7 +631,6 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
>
{(triggerRef) => (
<IconButton
className={PhoneHitSpread40}
fill="None"
onClick={handleOpenMenu}
ref={triggerRef}

View file

@ -10,7 +10,6 @@ import { getMxIdLocalPart } from '../../utils/matrix';
import * as css from './RoomViewTyping.css';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
import { PhoneHit } from '../../styles/PhoneHit.css';
export type RoomViewTypingProps = {
room: Room;
@ -112,13 +111,7 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>(
</>
)}
</Text>
<IconButton
className={PhoneHit}
title="Drop Typing Status"
size="300"
radii="Pill"
onClick={handleDropAll}
>
<IconButton title="Drop Typing Status" size="300" radii="Pill" onClick={handleDropAll}>
<Icon size="50" src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -31,7 +31,6 @@ import { getToday, getYesterday, timeDayMonthYear, timeHourMinute } from '../../
import { DatePicker, TimePicker } from '../../../components/time-date';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type JumpToTimeProps = {
onCancel: () => void;
@ -109,7 +108,7 @@ export function JumpToTime({ onCancel, onSubmit }: JumpToTimeProps) {
<Box grow="Yes">
<Text size="H4">Jump to Time</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={onCancel} radii="300">
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -1,20 +0,0 @@
import { describe, expect, it } from 'vitest';
import { EventStatus } from 'matrix-js-sdk';
import { isUnconfirmedLocalEcho } from './localEcho';
describe('isUnconfirmedLocalEcho', () => {
it.each([
EventStatus.ENCRYPTING,
EventStatus.QUEUED,
EventStatus.SENDING,
EventStatus.NOT_SENT,
EventStatus.CANCELLED,
])('hides %s', (status) => {
expect(isUnconfirmedLocalEcho({ status })).toBe(true);
});
it('shows a server-confirmed event and one the server has accepted (SENT)', () => {
expect(isUnconfirmedLocalEcho({ status: null })).toBe(false);
expect(isUnconfirmedLocalEcho({ status: EventStatus.SENT })).toBe(false);
});
});

View file

@ -1,10 +0,0 @@
import { EventStatus, MatrixEvent } from 'matrix-js-sdk';
/**
* A local echo the server has not accepted: the SDK adds it to the live timeline the moment
* sendMessage is called, marks it NOT_SENT on failure, and drops it on reload. Rendered like
* any message it looks delivered and then vanishes. The outbox (RoomOutbox) is the one place
* that shows unconfirmed sends, so the timeline hides these until the status is SENT.
*/
export const isUnconfirmedLocalEcho = (mEvent: Pick<MatrixEvent, 'status'>): boolean =>
mEvent.status !== null && mEvent.status !== undefined && mEvent.status !== EventStatus.SENT;

View file

@ -86,7 +86,6 @@ import colorMXID from '../../../../util/colorMXID';
import { useSkyInk } from '../../../hooks/useTheme';
import { userColorInk } from '../../scene/skyInk';
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
import { PhoneHitRoom26, PhoneHitSpread32 } from '../../../styles/PhoneHit.css';
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
@ -112,7 +111,7 @@ export const MessageQuickReactions = as<'div', MessageQuickReactionsProps>(
{recentEmojis.map((emoji) => (
<IconButton
key={emoji.unicode}
className={classNames(css.MessageQuickReaction, PhoneHitSpread32)}
className={css.MessageQuickReaction}
size="300"
variant="SurfaceVariant"
radii="Pill"
@ -460,12 +459,7 @@ export const MessageDeleteItem = as<
<Box grow="Yes">
<Text size="H4">Delete Message</Text>
</Box>
<IconButton
className={PhoneHitSpread32}
size="300"
onClick={handleClose}
radii="300"
>
<IconButton size="300" onClick={handleClose} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@ -595,12 +589,7 @@ export const MessageReportItem = as<
<Box grow="Yes">
<Text size="H4">Report Message</Text>
</Box>
<IconButton
className={PhoneHitSpread32}
size="300"
onClick={handleClose}
radii="300"
>
<IconButton size="300" onClick={handleClose} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@ -961,7 +950,6 @@ export const Message = as<'div', MessageProps>(
}
>
<IconButton
className={PhoneHitRoom26}
onClick={handleOpenEmojiBoard}
variant="SurfaceVariant"
size="300"
@ -973,7 +961,6 @@ export const Message = as<'div', MessageProps>(
</PopOut>
)}
<IconButton
className={PhoneHitRoom26}
onClick={onReplyClick}
data-event-id={mEvent.getId()}
variant="SurfaceVariant"
@ -984,7 +971,6 @@ export const Message = as<'div', MessageProps>(
</IconButton>
{!isThreadedMessage && (
<IconButton
className={PhoneHitRoom26}
onClick={(ev) => onReplyClick(ev, true)}
data-event-id={mEvent.getId()}
variant="SurfaceVariant"
@ -996,7 +982,6 @@ export const Message = as<'div', MessageProps>(
)}
{canEditEvent(mx, mEvent) && onEditId && (
<IconButton
className={PhoneHitRoom26}
onClick={() => onEditId(mEvent.getId())}
variant="SurfaceVariant"
size="300"
@ -1162,7 +1147,6 @@ export const Message = as<'div', MessageProps>(
}
>
<IconButton
className={PhoneHitRoom26}
variant="SurfaceVariant"
size="300"
radii="300"
@ -1336,7 +1320,6 @@ export const Event = as<'div', EventProps>(
}
>
<IconButton
className={PhoneHitRoom26}
variant="SurfaceVariant"
size="300"
radii="300"

View file

@ -54,7 +54,6 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getEditedEvent, getMentionContent, trimReplyFromFormattedBody } from '../../../utils/room';
import { mobileOrTablet } from '../../../utils/user-agent';
import { useComposingCheck } from '../../../hooks/useComposingCheck';
import { PhoneHitSpread32 } from '../../../styles/PhoneHit.css';
type MessageEditorProps = {
roomId: string;
@ -290,7 +289,6 @@ export const MessageEditor = as<'div', MessageEditorProps>(
</Box>
<Box gap="Inherit">
<IconButton
className={PhoneHitSpread32}
variant="SurfaceVariant"
size="300"
radii="300"
@ -324,7 +322,6 @@ export const MessageEditor = as<'div', MessageEditorProps>(
}
>
<IconButton
className={PhoneHitSpread32}
aria-pressed={anchor !== undefined}
onClick={
((evt) =>

View file

@ -28,7 +28,6 @@ import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { useOpenUserRoomProfile } from '../../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../../hooks/useSpace';
import { getMouseEventCords } from '../../../utils/dom';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export type ReactionViewerProps = {
room: Room;
@ -101,7 +100,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
<Box grow="Yes">
<Text size="H3" truncate>{`Reacted with :${selectedShortcode}:`}</Text>
</Box>
<IconButton className={PhoneHit} size="300" onClick={requestClose}>
<IconButton size="300" onClick={requestClose}>
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -86,7 +86,6 @@ import {
useGetMemberPowerTag,
} from '../../../hooks/useMemberPowerTag';
import { useRoomCreatorsTag } from '../../../hooks/useRoomCreatorsTag';
import { PhoneHit, PhoneHitSpread26 } from '../../../styles/PhoneHit.css';
type PinnedMessageProps = {
room: Room;
@ -147,7 +146,6 @@ function PinnedMessage({
</Chip>
{canPinEvent && (
<IconButton
className={PhoneHitSpread26}
data-event-id={eventId}
variant="Secondary"
size="300"
@ -459,7 +457,7 @@ export const RoomPinMenu = forwardRef<HTMLDivElement, RoomPinMenuProps>(
<Text size="H5">Pinned Messages</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} size="300" onClick={requestClose} radii="300">
<IconButton size="300" onClick={requestClose} radii="300">
<Icon src={Icons.Cross} size="400" />
</IconButton>
</Box>

View file

@ -28,7 +28,6 @@ import {
setSceneStyle,
type ServedSceneStyle,
} from './sceneStyle';
import { PhoneHit } from '../../styles/PhoneHit.css';
const STYLE_LABEL: Record<ServedSceneStyle, string> = { terrain: 'Terrain', ridge: 'Ridges' };
@ -150,7 +149,6 @@ export function SkyStateControl() {
}
>
<IconButton
className={PhoneHit}
variant="Background"
aria-label={`Sky: ${LABEL[state]}`}
title={`Sky: ${LABEL[state]}`}

View file

@ -33,7 +33,6 @@ import { About } from './about';
import { UseStateProvider } from '../../components/UseStateProvider';
import { stopPropagation } from '../../utils/keyboard';
import { LogoutDialog } from '../../components/LogoutDialog';
import { PhoneHit } from '../../styles/PhoneHit.css';
export enum SettingsPages {
GeneralPage,
@ -142,7 +141,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
</Box>
<Box shrink="No">
{screenSize === ScreenSize.Mobile && (
<IconButton className={PhoneHit} onClick={requestClose} variant="Background">
<IconButton onClick={requestClose} variant="Background">
<Icon src={Icons.Cross} />
</IconButton>
)}

View file

@ -8,7 +8,6 @@ import { LogoMark } from '../../../components/logo/logoMark.css';
import RatatoskrSVG from '../../../../../public/res/svg/ratatoskr.svg';
import { clearCacheAndReload } from '../../../../client/initMatrix';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type AboutProps = {
requestClose: () => void;
@ -26,7 +25,7 @@ export function About({ requestClose }: AboutProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -5,7 +5,6 @@ import { MatrixId } from './MatrixId';
import { Profile } from './Profile';
import { ContactInformation } from './ContactInfo';
import { IgnoredUserList } from './IgnoredUserList';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type AccountProps = {
requestClose: () => void;
@ -21,7 +20,7 @@ export function Account({ requestClose }: AccountProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -8,7 +8,6 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { isUserId } from '../../../utils/matrix';
import { useIgnoredUsers } from '../../../hooks/useIgnoredUsers';
import { useAlive } from '../../../hooks/useAlive';
import { PhoneHit } from '../../../styles/PhoneHit.css';
function IgnoreUserInput({ userList }: { userList: string[] }) {
const mx = useMatrixClient();
@ -68,7 +67,6 @@ function IgnoreUserInput({ userList }: { userList: string[] }) {
userId &&
!ignoring && (
<IconButton
className={PhoneHit}
type="reset"
onClick={handleReset}
size="300"

View file

@ -43,7 +43,6 @@ import { ModalWide } from '../../../styles/Modal.css';
import { createUploadAtom, UploadSuccess } from '../../../state/upload';
import { CompactUploadCardRenderer } from '../../../components/upload-card';
import { useCapabilities } from '../../../hooks/useCapabilities';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type ProfileProps = {
profile: UserProfile;
@ -186,12 +185,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
<Box grow="Yes">
<Text size="H4">Remove Avatar</Text>
</Box>
<IconButton
className={PhoneHit}
size="300"
onClick={() => setAlertRemove(false)}
radii="300"
>
<IconButton size="300" onClick={() => setAlertRemove(false)} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
@ -279,7 +273,6 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) {
hasChanges &&
!changingDisplayName && (
<IconButton
className={PhoneHit}
type="reset"
onClick={handleReset}
size="300"

View file

@ -13,7 +13,6 @@ import {
} from '../../../components/AccountDataEditor';
import { copyToClipboard } from '../../../utils/dom';
import { AccountData } from './AccountData';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type DeveloperToolsProps = {
requestClose: () => void;
@ -52,7 +51,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -29,7 +29,6 @@ import { LogoutDialog } from '../../../components/LogoutDialog';
import { stopPropagation } from '../../../utils/keyboard';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function DeviceTilePlaceholder() {
return (
@ -289,7 +288,6 @@ export function DeviceTile({
<SettingTile
before={
<IconButton
className={PhoneHit}
variant={deleted ? 'Critical' : 'Secondary'}
outlined={deleted}
radii="300"

View file

@ -26,7 +26,6 @@ import {
} from '../../../hooks/useSecretStorage';
import { useCrossSigningActive } from '../../../hooks/useCrossSigning';
import { BackupRestoreTile } from '../../../components/BackupRestore';
import { PhoneHit } from '../../../styles/PhoneHit.css';
function DevicesPlaceholder() {
return (
@ -75,7 +74,7 @@ export function Devices({ requestClose }: DevicesProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -35,7 +35,6 @@ import { stopPropagation } from '../../../utils/keyboard';
import { useAuthMetadata } from '../../../hooks/useAuthMetadata';
import { withSearchParam } from '../../../pages/pathUtils';
import { useAccountManagementActions } from '../../../hooks/useAccountManagement';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type VerificationStatusBadgeProps = {
verificationStatus: VerificationStatus;
@ -289,7 +288,6 @@ export function DeviceVerificationOptions() {
return (
<>
<IconButton
className={PhoneHit}
aria-pressed={!!menuCords}
variant="SurfaceVariant"
size="300"

View file

@ -5,7 +5,6 @@ import { GlobalPacks } from './GlobalPacks';
import { UserPack } from './UserPack';
import { ImagePack } from '../../../plugins/custom-emoji';
import { ImagePackView } from '../../../components/image-pack-view';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type EmojisStickersProps = {
requestClose: () => void;
@ -31,7 +30,7 @@ export function EmojisStickers({ requestClose }: EmojisStickersProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -43,7 +43,6 @@ import { allRoomsAtom } from '../../../state/room-list/roomList';
import { AccountDataEvent } from '../../../../types/matrix/accountData';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { stopPropagation } from '../../../utils/keyboard';
import { PhoneHit } from '../../../styles/PhoneHit.css';
function GlobalPackSelector({
packs,
@ -369,7 +368,6 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
<Box alignItems="Center" gap="300">
{removed ? (
<IconButton
className={PhoneHit}
size="300"
radii="Pill"
variant="Critical"
@ -380,7 +378,6 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
</IconButton>
) : (
<IconButton
className={PhoneHit}
size="300"
radii="Pill"
variant="Secondary"

View file

@ -50,7 +50,6 @@ import { useMessageLayoutItems } from '../../../hooks/useMessageLayout';
import { useMessageSpacingItems } from '../../../hooks/useMessageSpacing';
import { useDateFormatItems } from '../../../hooks/useDateFormat';
import { SequenceCardStyle } from '../styles.css';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type ThemeSelectorProps = {
themeNames: Record<string, string>;
@ -498,7 +497,6 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) {
>
{hasChanges ? (
<IconButton
className={PhoneHit}
tabIndex={-1}
onClick={handleReset}
type="reset"
@ -510,7 +508,6 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) {
</IconButton>
) : (
<IconButton
className={PhoneHit}
tabIndex={-1}
onClick={handleOpenMenu}
type="button"
@ -1018,7 +1015,7 @@ export function General({ requestClose }: GeneralProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -15,7 +15,6 @@ import {
} from '../../../hooks/useNotificationMode';
import { NotificationModeSwitcher } from './NotificationModeSwitcher';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const NOTIFY_MODE_OPS: NotificationModeOptions = {
highlight: true,
@ -76,7 +75,6 @@ function KeywordInput() {
keyword &&
!addingKeyword && (
<IconButton
className={PhoneHit}
type="reset"
onClick={handleReset}
size="300"
@ -120,14 +118,7 @@ function KeywordCross({ pushRule }: PushRulesProps) {
const removing = removeState.status === AsyncStatus.Loading;
return (
<IconButton
className={PhoneHit}
onClick={remove}
size="300"
radii="Pill"
variant="Secondary"
disabled={removing}
>
<IconButton onClick={remove} size="300" radii="Pill" variant="Secondary" disabled={removing}>
{removing ? <Spinner size="100" /> : <Icon src={Icons.Cross} size="100" />}
</IconButton>
);

View file

@ -8,7 +8,6 @@ import { KeywordMessagesNotifications } from './KeywordMessages';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type NotificationsProps = {
requestClose: () => void;
@ -24,7 +23,7 @@ export function Notifications({ requestClose }: NotificationsProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -17,7 +17,6 @@ import { Members } from '../common-settings/members';
import { DeveloperTools } from '../common-settings/developer-tools';
import { General } from './general';
import { Permissions } from './permissions';
import { PhoneHit } from '../../styles/PhoneHit.css';
type SpaceSettingsMenuItem = {
page: SpaceSettingsPage;
@ -118,7 +117,7 @@ export function SpaceSettings({ initialPage, requestClose }: SpaceSettingsProps)
</Box>
<Box shrink="No">
{screenSize === ScreenSize.Mobile && (
<IconButton className={PhoneHit} onClick={requestClose} variant="Background">
<IconButton onClick={requestClose} variant="Background">
<Icon src={Icons.Cross} />
</IconButton>
)}

View file

@ -13,7 +13,6 @@ import {
} from '../../common-settings/general';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type GeneralProps = {
requestClose: () => void;
@ -34,7 +33,7 @@ export function General({ requestClose }: GeneralProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -9,7 +9,6 @@ import { usePermissionGroups } from './usePermissionItems';
import { PermissionGroups, Powers, PowersEditor } from '../../common-settings/permissions';
import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../../hooks/useRoomPermissions';
import { PhoneHit } from '../../../styles/PhoneHit.css';
type PermissionsProps = {
requestClose: () => void;
@ -46,7 +45,7 @@ export function Permissions({ requestClose }: PermissionsProps) {
</Text>
</Box>
<Box shrink="No">
<IconButton className={PhoneHit} onClick={requestClose} variant="Surface">
<IconButton onClick={requestClose} variant="Surface">
<Icon src={Icons.Cross} />
</IconButton>
</Box>

View file

@ -23,7 +23,6 @@ import FocusTrap from 'focus-trap-react';
import { useDebounce } from '../../hooks/useDebounce';
import { stopPropagation } from '../../utils/keyboard';
import { PhoneHit } from '../../styles/PhoneHit.css';
export function ServerPicker({
server,
@ -130,7 +129,6 @@ export function ServerPicker({
}
>
<IconButton
className={PhoneHit}
onClick={handleOpenServerMenu}
variant={allowCustomServer ? 'Background' : 'Surface'}
size="300"

View file

@ -39,7 +39,6 @@ import { loginErrorMessage } from '../authMessages';
import { ForgotLink } from '../styles.css';
import { getResetPasswordPath } from '../../pathUtils';
import { stopPropagation } from '../../../utils/keyboard';
import { PhoneHit } from '../../../styles/PhoneHit.css';
function UsernameHint({ server }: { server: string }) {
const [anchor, setAnchor] = useState<RectCords>();
@ -95,7 +94,6 @@ function UsernameHint({ server }: { server: string }) {
}
>
<IconButton
className={PhoneHit}
tabIndex={-1}
onClick={handleOpenMenu}
type="button"

View file

@ -36,7 +36,6 @@ import { SyncStatus } from './SyncStatus';
import { AuthMetadataProvider } from '../../hooks/useAuthMetadata';
import { getFallbackSession } from '../../state/sessions';
import { AutoDiscovery } from './AutoDiscovery';
import { PhoneHit } from '../../styles/PhoneHit.css';
function ClientRootLoading() {
return (
@ -62,7 +61,6 @@ function ClientRootOptions({ mx }: { mx?: MatrixClient }) {
return (
<IconButton
className={PhoneHit}
style={{
position: 'absolute',
top: config.space.S100,

View file

@ -17,7 +17,6 @@ import {
} from '../../../components/page';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { CreateChat } from '../../../features/create-chat';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function DirectCreate() {
const mx = useMatrixClient();
@ -45,7 +44,7 @@ export function DirectCreate() {
<Box grow="Yes" alignItems="Center" gap="200">
<BackRouteHandler>
{(onBack) => (
<IconButton className={PhoneHit} onClick={onBack}>
<IconButton onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -37,7 +37,6 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { PageNav, PageNavContent, PageNavHeader } from '../../../components/page';
import { stopPropagation } from '../../../utils/keyboard';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function AddServer() {
const mx = useMatrixClient();
@ -97,12 +96,7 @@ export function AddServer() {
<Box grow="Yes">
<Text size="H4">Add Server</Text>
</Box>
<IconButton
className={PhoneHit}
size="300"
onClick={() => setDialog(false)}
radii="300"
>
<IconButton size="300" onClick={() => setDialog(false)} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>

View file

@ -18,7 +18,6 @@ import * as css from './style.css';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { PhoneHit } from '../../../styles/PhoneHit.css';
export function FeaturedRooms() {
const { featuredCommunities } = useClientConfig();
@ -34,7 +33,7 @@ export function FeaturedRooms() {
<Box shrink="No">
<BackRouteHandler>
{(onBack) => (
<IconButton className={PhoneHit} onClick={onBack}>
<IconButton onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

View file

@ -45,7 +45,6 @@ import { getMxIdServer } from '../../../utils/matrix';
import { stopPropagation } from '../../../utils/keyboard';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { PhoneHit } from '../../../styles/PhoneHit.css';
const useServerSearchParams = (searchParams: URLSearchParams): ExploreServerPathSearchParams =>
useMemo(
@ -500,7 +499,7 @@ export function PublicRooms() {
{screenSize === ScreenSize.Mobile && (
<BackRouteHandler>
{(onBack) => (
<IconButton className={PhoneHit} onClick={onBack}>
<IconButton onClick={onBack}>
<Icon src={Icons.ArrowLeft} />
</IconButton>
)}

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