mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: tint unread mail and draw the mail search, subject, quick reply and thread buttons like the webmail
This commit is contained in:
@@ -1459,10 +1459,9 @@ function makeStyles(c: ThemePalette) {
|
||||
color: c.text,
|
||||
},
|
||||
rowLabelAccount: {
|
||||
...typography.caption,
|
||||
fontWeight: '600',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 0.5,
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
fontWeight: '500',
|
||||
color: c.textSecondary,
|
||||
},
|
||||
counts: {
|
||||
|
||||
@@ -155,7 +155,7 @@ export function QuickReplyBox({ email, jmapAccountId, onMoreOptions, onSent, onF
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
placeholder={t('email_viewer.quick_reply_placeholder', 'Write a quick reply...')}
|
||||
placeholderTextColor={c.textMuted}
|
||||
placeholderTextColor={c.mutedForeground}
|
||||
style={styles.input}
|
||||
multiline
|
||||
editable={!sending}
|
||||
@@ -189,7 +189,7 @@ function makeStyles(c: ThemePalette) {
|
||||
borderWidth: 1,
|
||||
borderColor: c.border,
|
||||
borderRadius: radius.md,
|
||||
backgroundColor: c.surface,
|
||||
backgroundColor: c.background,
|
||||
padding: spacing.sm,
|
||||
gap: spacing.sm,
|
||||
},
|
||||
@@ -211,10 +211,10 @@ function makeStyles(c: ThemePalette) {
|
||||
gap: spacing.xs,
|
||||
backgroundColor: c.primary,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: 6,
|
||||
borderRadius: radius.full,
|
||||
minHeight: 36,
|
||||
borderRadius: radius.sm,
|
||||
},
|
||||
sendDisabled: { opacity: 0.5 },
|
||||
sendText: { ...typography.caption, color: c.primaryForeground, fontWeight: '600' },
|
||||
sendText: { ...typography.bodyMedium, color: c.primaryForeground },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,16 +74,16 @@ export function ThreadMessageCard({
|
||||
<MessageContent {...content} compact={false} onToggleStar={bare ? undefined : onToggleStar} />
|
||||
{!bare && (
|
||||
<View style={[styles.actions, !replyable && styles.actionsDisabled]}>
|
||||
<Pressable style={styles.actionBtn} onPress={reply('reply')} hitSlop={9}>
|
||||
<Reply size={16} color={c.textSecondary} />
|
||||
<Pressable style={styles.actionBtn} onPress={reply('reply')} hitSlop={6}>
|
||||
<Reply size={16} color={c.text} />
|
||||
<Text style={styles.actionLabel}>{t('email_viewer.reply', 'Reply')}</Text>
|
||||
</Pressable>
|
||||
<Pressable style={styles.actionBtn} onPress={reply('replyAll')} hitSlop={9}>
|
||||
<ReplyAll size={16} color={c.textSecondary} />
|
||||
<Pressable style={styles.actionBtn} onPress={reply('replyAll')} hitSlop={6}>
|
||||
<ReplyAll size={16} color={c.text} />
|
||||
<Text style={styles.actionLabel}>{t('email_viewer.reply_all', 'Reply All')}</Text>
|
||||
</Pressable>
|
||||
<Pressable style={styles.actionBtn} onPress={reply('forward')} hitSlop={9}>
|
||||
<Forward size={16} color={c.textSecondary} />
|
||||
<Pressable style={styles.actionBtn} onPress={reply('forward')} hitSlop={6}>
|
||||
<Forward size={16} color={c.text} />
|
||||
<Text style={styles.actionLabel}>{t('email_viewer.forward', 'Forward')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
@@ -159,13 +159,14 @@ function makeStyles(c: ThemePalette) {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: spacing.xs,
|
||||
// Webmail thread card: outline buttons, h-9, rounded-md.
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: 6,
|
||||
borderRadius: radius.full,
|
||||
minHeight: 36,
|
||||
borderRadius: radius.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: c.border,
|
||||
backgroundColor: c.surface,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
actionLabel: { ...typography.caption, color: c.textSecondary },
|
||||
actionLabel: { ...typography.bodyMedium, color: c.text },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '../components/icons';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens';
|
||||
import { spacing, radius, typography, componentSizes, mix, type ThemePalette } from '../theme/tokens';
|
||||
import { useColors, useResolvedTheme } from '../theme/colors';
|
||||
import { useTypography, useDensity } from '../theme/dynamic';
|
||||
import SidebarDrawer from '../components/SidebarDrawer';
|
||||
@@ -168,6 +168,7 @@ const EmailRow = React.memo(function EmailRow({
|
||||
style={({ pressed }) => [
|
||||
styles.emailRow,
|
||||
{ paddingVertical: density.rowPaddingVertical },
|
||||
unread && styles.emailRowUnread,
|
||||
pressed && styles.emailRowPressed,
|
||||
active && styles.emailRowActive,
|
||||
selected && styles.emailRowSelected,
|
||||
@@ -2002,10 +2003,9 @@ function makeStyles(c: ThemePalette) {
|
||||
// A minimum, not a fixed height: at a larger system font the text used
|
||||
// to be cut off at the top.
|
||||
minHeight: componentSizes.inputHeight, // h-10 = 40px
|
||||
backgroundColor: c.surface,
|
||||
borderRadius: radius.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: c.border,
|
||||
// Webmail mobile header: a filled pill, no border.
|
||||
backgroundColor: c.muted,
|
||||
borderRadius: radius.full,
|
||||
gap: spacing.sm,
|
||||
},
|
||||
// No lineHeight and no Android default padding on a TextInput: both clip
|
||||
@@ -2298,6 +2298,9 @@ function makeStyles(c: ThemePalette) {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: c.border, // border-b border-border
|
||||
},
|
||||
// Webmail: unread rows get bg-accent/30. Opaque, since the row covers
|
||||
// its swipe actions.
|
||||
emailRowUnread: { backgroundColor: mix(c.accent, c.background, 0.3) },
|
||||
emailRowPressed: { backgroundColor: c.surface },
|
||||
emailRowSelected: { backgroundColor: c.selection },
|
||||
// The message open next to the list (webmail: bg-selection with a primary
|
||||
|
||||
@@ -1683,11 +1683,12 @@ function makeStyles(c: ThemePalette) {
|
||||
subjectStar: {
|
||||
paddingTop: 4,
|
||||
},
|
||||
// Webmail viewer on a phone: text-lg font-bold tracking-tight.
|
||||
subjectText: {
|
||||
flex: 1,
|
||||
fontSize: 20,
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
lineHeight: 28,
|
||||
lineHeight: 26,
|
||||
color: c.text,
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mix, DARK_COLORS, LIGHT_COLORS } from '../tokens';
|
||||
|
||||
describe('mix', () => {
|
||||
it('blends like a translucent layer, but opaque', () => {
|
||||
expect(mix('#ffffff', '#000000', 0.5)).toBe('#808080');
|
||||
expect(mix('#1e3a8a', '#0a0a0a', 0)).toBe('#0a0a0a');
|
||||
expect(mix('#1e3a8a', '#0a0a0a', 1)).toBe('#1e3a8a');
|
||||
expect(mix('rgba(0,0,0,0.5)', '#0a0a0a', 0.3)).toBe('#0a0a0a');
|
||||
});
|
||||
|
||||
it('gives the webmail unread row tint (bg-accent/30)', () => {
|
||||
// #1e3a8a at 30% over #0a0a0a, and #dbeafe at 30% over white.
|
||||
expect(mix(DARK_COLORS.accent, DARK_COLORS.background, 0.3)).toBe('#101830');
|
||||
expect(mix(LIGHT_COLORS.accent, LIGHT_COLORS.background, 0.3)).toBe('#f4f9ff');
|
||||
});
|
||||
});
|
||||
@@ -329,3 +329,22 @@ export const componentSizes = {
|
||||
toggleHeight: 24, toggleWidth: 44, toggleThumb: 20,
|
||||
fab: 56, badgeSize: 16,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* `top` over `bottom` at `amount` opacity, as an opaque #rrggbb: the
|
||||
* webmail's `bg-accent/30` for a surface that must hide what is behind it
|
||||
* (a list row over its swipe actions). Takes #rrggbb; anything else
|
||||
* returns `bottom` unchanged.
|
||||
*/
|
||||
export function mix(top: string, bottom: string, amount: number): string {
|
||||
const parse = (hex: string) => {
|
||||
const m = /^#([0-9a-f]{6})$/i.exec(hex);
|
||||
if (!m) return null;
|
||||
const n = parseInt(m[1], 16);
|
||||
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
||||
};
|
||||
const a = parse(top);
|
||||
const b = parse(bottom);
|
||||
if (!a || !b) return bottom;
|
||||
return `#${a.map((v, i) => Math.round(v * amount + b[i] * (1 - amount)).toString(16).padStart(2, '0')).join('')}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user