mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: scale the reader's header and toolbar with the text size setting
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
// At 200% text size the reader's header and toolbar stayed at 100% while
|
||||
// the list and the body grew: their styles were built from the fixed
|
||||
// tokens. They now take the user's text size from useTypography, as the
|
||||
// list does. No React Native renderer in the test env, so this checks the
|
||||
// wiring.
|
||||
|
||||
const read = (rel: string) => fs.readFileSync(path.resolve(__dirname, '..', '..', rel), 'utf8');
|
||||
|
||||
const FILES = {
|
||||
'screens/EmailThreadScreen.tsx': 6,
|
||||
'components/email/MessageHeader.tsx': 1,
|
||||
'components/email/ThreadMessageCard.tsx': 2,
|
||||
};
|
||||
|
||||
describe('reader text size', () => {
|
||||
for (const [file, uses] of Object.entries(FILES)) {
|
||||
it(`builds the styles of ${file} at the user's text size`, () => {
|
||||
const src = read(file);
|
||||
expect(src).toMatch(/function makeStyles\(c: ThemePalette, typography: ScaledTypography = tokenTypography\)/);
|
||||
expect(src).not.toMatch(/makeStyles\(c\)/);
|
||||
expect((src.match(/const scaledType = useTypography\(\);\s+const styles = React\.useMemo\(\(\) => makeStyles\(c, scaledType\), \[c, scaledType\]\);/g) ?? []).length)
|
||||
.toBe(uses);
|
||||
});
|
||||
}
|
||||
|
||||
it('scales the subject line with the text size', () => {
|
||||
const src = read('screens/EmailThreadScreen.tsx');
|
||||
const subject = src.slice(src.indexOf(' subjectText: {'), src.indexOf(' subjectText: {') + 300);
|
||||
expect(subject).toMatch(/fontSize: typography\.h3\.fontSize,/);
|
||||
expect(subject).not.toMatch(/fontSize: 18,/);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
Star, ChevronDown, ChevronUp, Reply, Forward, ShieldCheck, ShieldAlert, ShieldQuestion, Lock,
|
||||
} from '../icons';
|
||||
import type { Email, EmailAddress, Identity } from '../../api/types';
|
||||
import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens';
|
||||
import { spacing, radius, typography, typography as tokenTypography, componentSizes, type ThemePalette } from '../../theme/tokens';
|
||||
import { useTypography } from '../../theme/dynamic';
|
||||
// The user's text size (Settings > Accessibility), as the list has it.
|
||||
type ScaledTypography = ReturnType<typeof useTypography>;
|
||||
import { useColors } from '../../theme/colors';
|
||||
import SenderAvatar from '../SenderAvatar';
|
||||
import { useSettingsStore } from '../../stores/settings-store';
|
||||
@@ -99,7 +102,8 @@ function DetailRow({ label, value, styles, mono }: { label: string; value?: stri
|
||||
*/
|
||||
export function MessageHeader({ email, identities, headerInfo, onToggleStar, onAddressPress, compact, jmapAccountId }: Props) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const locale = useLocaleStore((s) => s.locale);
|
||||
const timeFormat = useSettingsStore((s) => s.timeFormat);
|
||||
@@ -372,7 +376,7 @@ export function MessageHeader({ email, identities, headerInfo, onToggleStar, onA
|
||||
);
|
||||
}
|
||||
|
||||
function makeStyles(c: ThemePalette) {
|
||||
function makeStyles(c: ThemePalette, typography: ScaledTypography = tokenTypography) {
|
||||
return StyleSheet.create({
|
||||
block: {
|
||||
backgroundColor: c.background,
|
||||
@@ -430,7 +434,7 @@ function makeStyles(c: ThemePalette) {
|
||||
detailRow: { flexDirection: 'row', gap: spacing.sm },
|
||||
detailLabel: { ...typography.caption, color: c.textMuted, width: 92 },
|
||||
detailValue: { ...typography.caption, color: c.text, flex: 1 },
|
||||
detailMono: { fontFamily: 'monospace', fontSize: 11 },
|
||||
detailMono: { fontFamily: 'monospace', fontSize: Math.round((11 * typography.caption.fontSize) / 12) },
|
||||
chips: { flexDirection: 'row', flexWrap: 'wrap', gap: spacing.xs, marginBottom: spacing.xs },
|
||||
chip: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -2,7 +2,10 @@ import React from 'react';
|
||||
import { View, Text, StyleSheet, Pressable } from 'react-native';
|
||||
import { Paperclip, Reply, ReplyAll, Forward, Star } from '../icons';
|
||||
import type { Email } from '../../api/types';
|
||||
import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens';
|
||||
import { spacing, radius, typography, typography as tokenTypography, componentSizes, type ThemePalette } from '../../theme/tokens';
|
||||
import { useTypography } from '../../theme/dynamic';
|
||||
// The user's text size (Settings > Accessibility), as the list has it.
|
||||
type ScaledTypography = ReturnType<typeof useTypography>;
|
||||
import { useColors } from '../../theme/colors';
|
||||
import { useSettingsStore } from '../../stores/settings-store';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
@@ -42,7 +45,8 @@ export function ThreadMessageCard({
|
||||
}: Props) {
|
||||
const { email, onToggleStar } = content;
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const locale = useLocaleStore((s) => s.locale);
|
||||
const timeFormat = useSettingsStore((s) => s.timeFormat);
|
||||
@@ -113,7 +117,8 @@ export function ThreadMessageCard({
|
||||
*/
|
||||
export function ThreadCardPlaceholder() {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
return (
|
||||
<View style={styles.collapsed}>
|
||||
<View style={styles.placeholderAvatar} />
|
||||
@@ -125,7 +130,7 @@ export function ThreadCardPlaceholder() {
|
||||
);
|
||||
}
|
||||
|
||||
function makeStyles(c: ThemePalette) {
|
||||
function makeStyles(c: ThemePalette, typography: ScaledTypography = tokenTypography) {
|
||||
return StyleSheet.create({
|
||||
collapsed: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -20,7 +20,10 @@ import { cardReplyActions } from '../lib/thread-card-actions';
|
||||
import { NewTagRow } from '../components/NewTagRow';
|
||||
import { QuickRuleSheet } from '../components/filters/QuickRuleSheet';
|
||||
import { isSieveSupported } from '../api/sieve';
|
||||
import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens';
|
||||
import { spacing, radius, typography, typography as tokenTypography, componentSizes, type ThemePalette } from '../theme/tokens';
|
||||
import { useTypography } from '../theme/dynamic';
|
||||
// The user's text size (Settings > Accessibility), as the list has it.
|
||||
type ScaledTypography = ReturnType<typeof useTypography>;
|
||||
import { useColors, useResolvedTheme } from '../theme/colors';
|
||||
import { MoveSheet } from '../components/MoveSheet';
|
||||
import { ThreadMessageCard, ThreadCardPlaceholder } from '../components/email/ThreadMessageCard';
|
||||
@@ -97,7 +100,8 @@ export default function EmailThreadScreen(props: Props) {
|
||||
|
||||
function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Props) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
const { t } = useLocaleStore();
|
||||
// The JMAP account the message lives in, as the screen that opened it named
|
||||
// it (undefined = the user's own). Never the open folder's: that may be
|
||||
@@ -1398,7 +1402,8 @@ function MoreMenuSheet({
|
||||
onForwardAsAttachment, onRules, onViewSource, onExport,
|
||||
}: MoreMenuSheetProps) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const insets = useSafeAreaInsets();
|
||||
const slideY = React.useRef(new Animated.Value(600)).current;
|
||||
@@ -1564,7 +1569,8 @@ interface TagMenuSheetProps {
|
||||
|
||||
function TagMenuSheet({ visible, onClose, keywords, activeKeywords, onToggle }: TagMenuSheetProps) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const insets = useSafeAreaInsets();
|
||||
const slideY = React.useRef(new Animated.Value(500)).current;
|
||||
@@ -1649,7 +1655,8 @@ function MoreMenuItem({
|
||||
icon, label, onPress, trailing,
|
||||
}: { icon: React.ReactNode; label: string; onPress?: () => void; trailing?: React.ReactNode }) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
@@ -1666,7 +1673,8 @@ function ToolbarButton({
|
||||
icon, label, onPress,
|
||||
}: { icon: React.ReactNode; label: string; onPress?: () => void }) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
// Icons only in a short (landscape) window, which leaves room for the message.
|
||||
const labelsSetting = useSettingsStore((s) => s.showToolbarLabels);
|
||||
const { shortHeight } = useLayout();
|
||||
@@ -1688,7 +1696,8 @@ function BottomBarButton({
|
||||
icon, label, onPress, disabled,
|
||||
}: { icon: React.ReactNode; label: string; onPress?: () => void; disabled?: boolean }) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const scaledType = useTypography();
|
||||
const styles = React.useMemo(() => makeStyles(c, scaledType), [c, scaledType]);
|
||||
// Icons only in a short (landscape) window, which leaves room for the message.
|
||||
const labelsSetting = useSettingsStore((s) => s.showToolbarLabels);
|
||||
const { shortHeight } = useLayout();
|
||||
@@ -1710,7 +1719,7 @@ function BottomBarButton({
|
||||
);
|
||||
}
|
||||
|
||||
function makeStyles(c: ThemePalette) {
|
||||
function makeStyles(c: ThemePalette, typography: ScaledTypography = tokenTypography) {
|
||||
return StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: c.background },
|
||||
|
||||
@@ -1786,9 +1795,10 @@ function makeStyles(c: ThemePalette) {
|
||||
// Webmail viewer on a phone: text-lg font-bold tracking-tight.
|
||||
subjectText: {
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
// 18 / 26 at the default text size, scaled with it.
|
||||
fontSize: typography.h3.fontSize,
|
||||
fontWeight: '700',
|
||||
lineHeight: 26,
|
||||
lineHeight: Math.round((26 * typography.h3.fontSize) / 18),
|
||||
color: c.text,
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user