diff --git a/src/app/components/editor/Editor.preview.tsx b/src/app/components/editor/Editor.preview.tsx deleted file mode 100644 index b760dddc..00000000 --- a/src/app/components/editor/Editor.preview.tsx +++ /dev/null @@ -1,84 +0,0 @@ -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 ( - <> - setOpen(!open)}> - - - }> - - setOpen(false), - clickOutsideDeactivates: true, - escapeDeactivates: stopPropagation, - }} - > - -
- - - - } - after={ - <> - setToolbar(!toolbar)} - aria-pressed={toolbar} - > - - - - - - - - - - } - bottom={ - toolbar && ( -
- - -
- ) - } - /> -
-
-
-
-
- - ); -} diff --git a/src/app/components/url-preview/UrlPreviewCard.tsx b/src/app/components/url-preview/UrlPreviewCard.tsx index ee8967af..5d40e10c 100644 --- a/src/app/components/url-preview/UrlPreviewCard.tsx +++ b/src/app/components/url-preview/UrlPreviewCard.tsx @@ -1,3 +1,4 @@ +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'; @@ -10,6 +11,7 @@ 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'; @@ -181,7 +183,10 @@ export const UrlPreviewHolder = as<'div'>(({ children, ...props }, ref) => { <>
(({ children, ...props }, ref) => { <>
= { + 'features/estate/EstateNavigator.tsx': { + indices: [0], + why: 'Find a room sits in a css.Targets row that already gives it a 44 px phone target', + }, + 'features/lobby/LobbyHeader.tsx': { + indices: [1], + why: 'Members button, hidden on mobile', + }, +}; + +const tsxFiles = (dir: string): string[] => + readdirSync(`${process.cwd()}/src/app/${dir}`, { withFileTypes: true }).flatMap((e) => { + if (e.isDirectory()) return tsxFiles(`${dir}${e.name}/`); + return e.name.endsWith('.tsx') ? [`${dir}${e.name}`] : []; + }); + +describe('phone hit areas: every IconButton is covered or named', () => { + it('lists exactly the IconButtons without a PhoneHit class', () => { + const bare: Record = {}; + tsxFiles('').forEach((file) => { + const src = readFileSync(`${process.cwd()}/src/app/${file}`, 'utf8'); + if (!src.includes(' (c === null ? [i] : [])); + if (missing.length > 0) bare[file] = missing; + }); + expect(bare).toEqual( + Object.fromEntries(Object.entries(EXEMPT).map(([f, e]) => [f, e.indices])) + ); + }); +}); + +// UrlPreviewCard's arrows are absolutely positioned by their own class; PhoneHit would set position: relative on them +// and win or lose by stylesheet order, so they carry the variant that adds only the hit area. +describe('phone hit area of a button positioned by its own class', () => { + it('the arrows carry PhoneHitInPlace', () => { + expectPhoneHitClasses('components/url-preview/UrlPreviewCard.tsx', [ + 'PhoneHitInPlace', + 'PhoneHitInPlace', + ]); + }); + + it('PhoneHitInPlace draws the ::after and sets no position of its own', async () => { + const lists = await classLists('styles/PhoneHit.css.ts'); + expect(lists.PhoneHitInPlace).toHaveLength(1); + const css = await emitted('styles/PhoneHit.css.ts'); + const rules = [...css.matchAll(/^([^{}\n]*PhoneHitInPlace__[^{}\n]*) \{$/gm)].map((m) => m[1]); + expect(rules.length).toBeGreaterThan(0); + rules.forEach((sel) => expect(sel).toMatch(/::after$/)); + }); +}); diff --git a/src/app/phoneHitTestKit.ts b/src/app/phoneHitTestKit.ts index 896b917b..d8449cba 100644 --- a/src/app/phoneHitTestKit.ts +++ b/src/app/phoneHitTestKit.ts @@ -168,7 +168,7 @@ export const phoneHitOf = (tag: string) => { /** The PhoneHit* class of each IconButton of a source, in source order; null when it has none or the name is not * the one imported from PhoneHit.css. */ -const phoneHitClasses = (src: string) => { +export const phoneHitClasses = (src: string) => { const sf = tsx(src); const imported = importedHits(sf); return elements(sf).map((el) => hitOf(el, imported)); diff --git a/src/app/phoneTapTargets.test.ts b/src/app/phoneTapTargets.test.ts index 5a5394e7..192f8414 100644 --- a/src/app/phoneTapTargets.test.ts +++ b/src/app/phoneTapTargets.test.ts @@ -102,13 +102,16 @@ describe('phone icon-button hit areas', () => { expect(css).toMatch(/::after \{[^}]*transform: translate\(-50%, -50%\);/); // the box anchors to its own button, receives the tap, and sits above nothing it should not expect(css).toMatch(/@media [^{]+ \{\n\s*\.PhoneHit_PhoneHit__\w+ \{\n\s*position: relative;/); - const afterDecls = [...css.matchAll(/::after \{\n([^}]*)\}/g)].flatMap((m) => + // PhoneHit and PhoneHitInPlace each draw it; the Spread and Room classes compose PhoneHit + const afters = [...css.matchAll(/::after \{\n([^}]*)\}/g)]; + expect(afters).toHaveLength(2); + const afterDecls = afters.flatMap((m) => m[1] .split('\n') .map((l) => l.trim().split(':')[0]) .filter(Boolean) ); - expect([...afterDecls].sort()).toEqual([ + expect([...new Set(afterDecls)].sort()).toEqual([ 'content', 'height', 'left', @@ -117,8 +120,9 @@ describe('phone icon-button hit areas', () => { 'transform', 'width', ]); - // nothing on a phone moves a button off its own anchor: only relative (the button) and absolute (the box) + // nothing on a phone moves a button off its own anchor: only relative (the button) and absolute (the boxes) expect([...css.matchAll(/position: (\w+);/g)].map((m) => m[1]).sort()).toEqual([ + 'absolute', 'absolute', 'relative', ]); diff --git a/src/app/styles/PhoneHit.css.ts b/src/app/styles/PhoneHit.css.ts index 9863851b..e971ecb3 100644 --- a/src/app/styles/PhoneHit.css.ts +++ b/src/app/styles/PhoneHit.css.ts @@ -29,6 +29,10 @@ export const PhoneHit = style({ selectors: { '&::after': hitArea }, }); +/** For a button that is already positioned (absolute or fixed) by its own class: PhoneHit's `position: relative` + * would fight that class by stylesheet order, and the positioned button already contains its ::after. */ +export const PhoneHitInPlace = style({ selectors: { '&::after': hitArea } }); + const spreads = new Map(); /** An icon button in a row, `visual` px wide: the row opens up so its 44 px hit area meets its neighbours'. */