diff --git a/src/widgets/layouts/calendar.tsx b/src/widgets/layouts/calendar.tsx
index 171a4437..ef25e63e 100644
--- a/src/widgets/layouts/calendar.tsx
+++ b/src/widgets/layouts/calendar.tsx
@@ -192,7 +192,7 @@ export const AgendaLayout: Layout = ({ s, p, f, now, height }) => {
iconName="calendar"
title={f.t('widgets.calendar.agenda', 'Agenda')}
click={open(links.calendar())}
- trailing={}
+ trailing={}
/>
);
const days = agendaDays(s.calendar.events, now, 14);
@@ -1008,7 +1008,8 @@ export const UpNextLayout: Layout = ({ s, p, f, now }) => {
: f.t('widgets.calendar.join', 'Join');
return (
-
+ // 36dp buttons in a 48dp tap area (slop 6), which takes 6dp of the bottom padding.
+
@@ -1040,9 +1041,9 @@ export const UpNextLayout: Layout = ({ s, p, f, now }) => {
{e.videoUrl ? (
-
+
) : (
-
+
)}
diff --git a/src/widgets/layouts/hubs.tsx b/src/widgets/layouts/hubs.tsx
index 1550e872..99deeb3b 100644
--- a/src/widgets/layouts/hubs.tsx
+++ b/src/widgets/layouts/hubs.tsx
@@ -432,7 +432,7 @@ export const RecentFilesLayout: Layout = ({ s, p, f, now, height }) => {
title={f.t('widgets.shortcuts.files', 'Files')}
count={f.t('widgets.files.recent', 'Recently changed')}
click={open(links.files())}
- trailing={}
+ trailing={}
/>
);
const items = s.files.items;
diff --git a/src/widgets/layouts/mail.tsx b/src/widgets/layouts/mail.tsx
index 50dcfeeb..23e28832 100644
--- a/src/widgets/layouts/mail.tsx
+++ b/src/widgets/layouts/mail.tsx
@@ -60,9 +60,9 @@ export const InboxLayout: Layout = ({ s, p, f, now, width, height }) => {
- {compact || narrow ? null : }
+ {compact || narrow ? null : }
-
+
)}
/>
@@ -115,7 +115,9 @@ export const TriageLayout: Layout = ({ s, p, f, now, width, widgetId, local }) =
const allFit = buttonWidth(labels.archive) + buttonWidth(labels.trash) + buttonWidth(labels.reply) <= room;
const replyFits = 2 * 32 + buttonWidth(labels.reply) <= room;
return (
-
+ // The buttons draw 32dp tall in a 48dp tap area (slop 8), which takes the
+ // gap above them and 8dp of the bottom padding.
+
@@ -139,15 +141,14 @@ export const TriageLayout: Layout = ({ s, p, f, now, width, widgetId, local }) =
{current.preview && !notice ? : null}
{notice ? : null}
-
-
+
-
+
-
+
-
+
);
diff --git a/src/widgets/layouts/plans.tsx b/src/widgets/layouts/plans.tsx
index b56a0214..4551989e 100644
--- a/src/widgets/layouts/plans.tsx
+++ b/src/widgets/layouts/plans.tsx
@@ -98,6 +98,8 @@ export const InvitationsLayout: Layout = ({ s, p, f, height }) => {
const pad = height < 140 ? 10 : 14;
const showPeople = inv.participants.length > 0 && height >= 132;
const buttonHeight = height < 116 ? 28 : 32;
+ // Tap area of 48dp around the buttons; it takes the bottom padding.
+ const slop = Math.min(pad, Math.round((48 - buttonHeight) / 2));
const label = (
inv.organizerName
@@ -111,7 +113,7 @@ export const InvitationsLayout: Layout = ({ s, p, f, height }) => {
const notice = noticeFor(s.notice, 'rsvp');
return (
-
+
{
label={f.t('widgets.invites.accept', 'Accept')}
flex={1}
height={buttonHeight}
+ slop={slop}
click={reply('accepted')}
/>
-
+
-
+
);
@@ -360,7 +363,9 @@ export const FreeTimeLayout: Layout = ({ s, p, f, now, width, height }) => {
};
return (
-
+ // The full-width button draws tier.button tall; its tap area also takes
+ // the bottom padding (slop).
+
{
) : null}
- {tier.button ? : null}
{tier.button ? (
@@ -437,6 +441,8 @@ export const FreeTimeLayout: Layout = ({ s, p, f, now, width, height }) => {
label={f.t('widgets.free.mail', 'Mail my free times')}
flex={1}
height={tier.button}
+ // Above, the tap area takes the gap and any height to spare; below, the padding.
+ slop={{ top: Math.min(tier.pad, tier.gap + Math.max(0, height - freeTierHeight(tier))), bottom: tier.pad }}
click={mail}
/>
@@ -481,7 +487,7 @@ export const TasksLayout: Layout = ({ s, p, f, now, height }) => {
title={f.t('widgets.tasks.title', 'Tasks')}
count={f.t('widgets.tasks.open_count', '{count} open', { count: openCount })}
click={open(links.calendar())}
- trailing={}
+ trailing={}
/>
{notice ? (
@@ -665,6 +671,8 @@ export const BirthdaysLayout: Layout = ({ s, p, f, now, width, height }) => {
variant="primary"
iconName="send"
label={narrow ? undefined : f.t('widgets.birthdays.send_wishes', 'Send wishes')}
+ // The row is 50dp; the tap area takes its full height.
+ slop={Math.floor((BIRTHDAY_ROW_HEIGHT - 32) / 2)}
click={open(links.compose({ to: wish, subject: f.t('widgets.birthdays.wishes_subject', 'Happy birthday!') }))}
/>
) : null}
diff --git a/src/widgets/primitives.tsx b/src/widgets/primitives.tsx
index dd5b401b..e82b8dfc 100644
--- a/src/widgets/primitives.tsx
+++ b/src/widgets/primitives.tsx
@@ -222,6 +222,7 @@ export function Button({
click,
height = 32,
flex,
+ slop = 0,
}: {
p: WidgetPalette;
label?: string;
@@ -230,18 +231,26 @@ export function Button({
click?: WidgetClick;
height?: number;
flex?: number;
+ /**
+ * Transparent tap area above and below the drawn button, so a 32dp button
+ * still takes a 48dp tap. Take the same amount off the spacing around it.
+ */
+ slop?: number | { top: number; bottom: number };
}) {
const primary = variant === 'primary';
+ const [slopTop, slopBottom] = typeof slop === 'number' ? [slop, slop] : [slop.top, slop.bottom];
const fg = primary ? p.primaryFg : p.fg;
- return (
+ const face = (
: null}
);
+ if (!slop) return face;
+ return (
+
+ {face}
+
+ );
}
/** Round primary action (compose, new event), the webmail's floating button. */
@@ -260,15 +282,18 @@ export function Fab({
iconName,
size = 32,
click,
+ slop = 0,
}: {
p: WidgetPalette;
iconName: IconName;
size?: number;
click?: WidgetClick;
+ /** Transparent tap area around the button (see Button). */
+ slop?: number;
}) {
- return (
+ const face = (
);
+ if (!slop) return face;
+ return (
+
+ {face}
+
+ );
}
/** Icon-only ghost button (search in a header). */