fix: fit settings > calendar to touch screens and hide import and subscribe in the demo

This commit is contained in:
Linus Rath
2026-10-28 12:56:51 +01:00
parent 5be162d604
commit b957bbcd8a
31 changed files with 125 additions and 15 deletions
@@ -0,0 +1,68 @@
import { render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { Calendar } from '@/lib/jmap/types';
import { useAuthStore } from '@/stores/auth-store';
import { useCalendarStore } from '@/stores/calendar-store';
// Settings > Calendar on a phone: no right-click hint on a touch screen, a
// colour dot that can be hit, and no Import or Subscribe in the demo, which
// Calendar itself hides there too.
const env = vi.hoisted(() => ({ touch: false, demoMode: false }));
vi.mock('@/hooks/use-media-query', () => ({
useMediaQuery: (query: string) => (query === '(pointer: coarse)' ? env.touch : false),
useIsMobile: () => env.touch,
useIsDesktop: () => !env.touch,
}));
vi.mock('@/hooks/use-config', () => ({ useConfig: () => ({ demoMode: env.demoMode }) }));
const { CalendarManagementSettings } = await import('../calendar-management-settings');
const work = {
id: 'c', name: 'Work', color: '#3b82f6', isDefault: true, isShared: false, shareWith: null,
myRights: { mayShare: true, mayDelete: true, mayWriteAll: true },
} as unknown as Calendar;
const initial = { auth: useAuthStore.getState(), calendar: useCalendarStore.getState() };
beforeEach(() => {
env.touch = false;
env.demoMode = false;
useAuthStore.setState({ client: { getAccountId: () => 'acc' }, serverUrl: null, username: null } as never);
useCalendarStore.setState({ calendars: [work], icalSubscriptions: [] } as never);
});
afterEach(() => {
useAuthStore.setState(initial.auth, true);
useCalendarStore.setState(initial.calendar, true);
});
describe('Settings > Calendar on a phone', () => {
it('mentions right-click only where there is a mouse', () => {
const { unmount } = render(<CalendarManagementSettings />);
expect(screen.getByText('description')).toBeInTheDocument();
unmount();
env.touch = true;
render(<CalendarManagementSettings />);
expect(screen.queryByText('description')).toBeNull();
expect(screen.getByText('description_touch')).toBeInTheDocument();
});
it('gives the colour dot a 44px hit area on touch', () => {
render(<CalendarManagementSettings />);
const dot = screen.getByRole('button', { name: 'change_color' });
expect(dot.className).toContain('pointer-coarse:after:min-h-11');
expect(dot.className).toContain('pointer-coarse:after:min-w-11');
});
it('offers Import and Subscribe only outside the demo', () => {
const { unmount } = render(<CalendarManagementSettings />);
// Both dialogs' titles are "title" here: one button each.
expect(screen.getAllByRole('button', { name: 'title' })).toHaveLength(2);
unmount();
env.demoMode = true;
render(<CalendarManagementSettings />);
expect(screen.queryByRole('button', { name: 'title' })).toBeNull();
expect(screen.getByRole('button', { name: 'add_calendar' })).toBeInTheDocument();
});
});
@@ -29,6 +29,9 @@ import { IS_LITE } from '@/lib/lite';
import { shareRecipientNames, useShareRecipientLabels } from '@/hooks/use-share-recipient-labels';
import { calendarAccountKey, moveCalendar } from '@/lib/calendar-sort';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { TOUCH_HIT_AREA } from '@/components/ui/button';
import { useMediaQuery } from '@/hooks/use-media-query';
import { useConfig } from '@/hooks/use-config';
function CalendarColorPicker({
value,
@@ -291,6 +294,10 @@ export function CalendarManagementSettings() {
const colorPickerRef = useRef<HTMLDivElement>(null);
const [draggingId, setDraggingId] = useState<string | null>(null);
const { contextMenu, openContextMenu, openContextMenuFrom, closeContextMenu, menuRef } = useContextMenu<Calendar>();
// No right-click on a touch screen, so the hint about it goes there.
const touch = useMediaQuery('(pointer: coarse)');
// The demo has no server to import into or subscribe from, as in Calendar.
const { demoMode } = useConfig();
const listedCalendars = calendars
.filter(cal => !isSubscriptionCalendar(cal.id))
@@ -620,7 +627,7 @@ export function CalendarManagementSettings() {
};
return (
<SettingsSection id="calendars" title={t('title')} description={t('description')}>
<SettingsSection id="calendars" title={t('title')} description={touch ? t('description_touch') : t('description')}>
{renderRowMenu()}
<div className="space-y-2">
{listedCalendars.map((cal) => {
@@ -686,7 +693,10 @@ export function CalendarManagementSettings() {
<button
type="button"
onClick={() => setColorPickerId(colorPickerId === cal.id ? null : cal.id)}
className="w-5 h-5 rounded-full shrink-0 transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
className={cn(
"block w-5 h-5 rounded-full shrink-0 transition-transform hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
TOUCH_HIT_AREA,
)}
style={{ backgroundColor: color }}
title={t('change_color')}
aria-label={t('change_color')}
@@ -811,31 +821,34 @@ export function CalendarManagementSettings() {
isLoading={isLoading}
/>
) : (
<div className="flex gap-2">
// Stacked on a phone: side by side the three labels wrapped.
<div className="flex flex-col sm:flex-row gap-2">
<button
type="button"
onClick={() => setIsCreating(true)}
className="flex items-center gap-2 flex-1 py-2.5 px-3 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md border border-dashed border-border transition-colors"
className="flex items-center gap-2 flex-1 py-2.5 px-3 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md border border-dashed border-border transition-colors whitespace-nowrap"
>
<Plus className="w-4 h-4" />
{t('add_calendar')}
</button>
<button
type="button"
data-search-label={tImport('title')}
onClick={() => setShowImportModal(true)}
className="flex items-center gap-2 py-2.5 px-3 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md border border-dashed border-border transition-colors"
>
<Upload className="w-4 h-4" />
{tImport('title')}
</button>
{!demoMode && (
<button
type="button"
data-search-label={tImport('title')}
onClick={() => setShowImportModal(true)}
className="flex items-center gap-2 py-2.5 px-3 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md border border-dashed border-border transition-colors whitespace-nowrap"
>
<Upload className="w-4 h-4" />
{tImport('title')}
</button>
)}
{/* ICS URL subscriptions refresh via /api/fetch-ical, absent in the static build. */}
{!IS_LITE && (
{!IS_LITE && !demoMode && (
<button
type="button"
data-search-label={tSub('title')}
onClick={() => setShowSubscriptionModal(true)}
className="flex items-center gap-2 py-2.5 px-3 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md border border-dashed border-border transition-colors"
className="flex items-center gap-2 py-2.5 px-3 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md border border-dashed border-border transition-colors whitespace-nowrap"
>
<Globe className="w-4 h-4" />
{tSub('title')}
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "إدارة التقويمات",
"description": "أنشئ تقويماتك وأعد تسميتها وخصّصها. انقر بالزر الأيمن على تقويم في الشريط الجانبي لتغيير لونه بسرعة.",
"description_touch": "أنشئ تقويماتك وأعد تسميتها وخصّصها.",
"name": "الاسم",
"name_placeholder": "اسم التقويم",
"color": "اللون",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Gestió de calendaris",
"description": "Creeu, canvieu el nom i personalitzeu els calendaris. Feu clic dret a un calendari a la barra lateral per canviar-ne ràpidament el color.",
"description_touch": "Creeu, canvieu el nom i personalitzeu els calendaris.",
"name": "Nom",
"name_placeholder": "Nom del calendari",
"color": "Color",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Správa kalendáře",
"description": "Vytvářejte, přejmenovávejte a přizpůsobujte si své kalendáře. Klikněte pravým tlačítkem na kalendář v postranním panelu pro rychlou změnu jeho barvy.",
"description_touch": "Vytvářejte, přejmenovávejte a přizpůsobujte si své kalendáře.",
"name": "Název",
"name_placeholder": "Název kalendáře",
"color": "Barva",
+1
View File
@@ -4018,6 +4018,7 @@
"management": {
"title": "Kalenderadministration",
"description": "Opret, omdøb og tilpas dine kalendere. Højreklik på en kalender i sidepanelet for hurtigt at ændre dens farve.",
"description_touch": "Opret, omdøb og tilpas dine kalendere.",
"name": "Navn",
"name_placeholder": "Kalendernavn",
"color": "Farve",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Kalenderverwaltung",
"description": "Erstellen, umbenennen und anpassen Ihrer Kalender. Rechtsklick auf einen Kalender in der Seitenleiste, um die Farbe schnell zu ändern.",
"description_touch": "Erstellen, benennen und gestalten Sie Ihre Kalender.",
"name": "Name",
"name_placeholder": "Kalendername",
"color": "Farbe",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Calendar management",
"description": "Create, rename, and customize your calendars. Right-click a calendar in the sidebar to quickly change its color.",
"description_touch": "Create, rename, and customize your calendars.",
"name": "Name",
"name_placeholder": "Calendar name",
"color": "Color",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Gestión de calendarios",
"description": "Crea, renombra y personaliza tus calendarios. Haz clic derecho en un calendario en la barra lateral para cambiar su color rápidamente.",
"description_touch": "Crea, renombra y personaliza tus calendarios.",
"name": "Nombre",
"name_placeholder": "Nombre del calendario",
"color": "Color",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Kalendrite haldamine",
"description": "Loo, nimeta ümber ja kohanda oma kalendreid. Kalendri värvi kiireks muutmiseks tee küljeribal kalendril paremklõps.",
"description_touch": "Loo, nimeta ümber ja kohanda oma kalendreid.",
"name": "Nimi",
"name_placeholder": "Kalendri nimi",
"color": "Värv",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "مدیریت تقویم",
"description": "ایجاد، تغییر نام و سفارشی‌سازی تقویم‌ها.",
"description_touch": "ایجاد، تغییر نام و سفارشی‌سازی تقویم‌ها.",
"name": "نام",
"name_placeholder": "نام تقویم",
"color": "رنگ",
+1
View File
@@ -4018,6 +4018,7 @@
"management": {
"title": "Gestion des calendriers",
"description": "Créez, renommez et personnalisez vos calendriers. Clic droit sur un calendrier dans la barre latérale pour changer rapidement sa couleur.",
"description_touch": "Créez, renommez et personnalisez vos calendriers.",
"name": "Nom",
"name_placeholder": "Nom du calendrier",
"color": "Couleur",
+1
View File
@@ -3901,6 +3901,7 @@
"management": {
"title": "ניהול לוח שנה",
"description": "צור, שנה שם והתאם אישית את היומנים שלך. לחץ לחיצה ימנית על לוח שנה בסרגל הצד כדי לשנות במהירות את צבעו.",
"description_touch": "צור, שנה שם והתאם אישית את היומנים שלך.",
"name": "שֵׁם",
"name_placeholder": "שם לוח שנה",
"color": "צבע",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Naptár kezelés",
"description": "Naptárak létrehozása, átnevezése és testreszabása. Kattints jobb gombbal egy naptárra az oldalsávban a szín gyors módosításához.",
"description_touch": "Naptárak létrehozása, átnevezése és testreszabása.",
"name": "Név",
"name_placeholder": "Naptár neve",
"color": "Szín",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Gestione calendari",
"description": "Crea, rinomina e personalizza i tuoi calendari. Fai clic destro su un calendario nella barra laterale per cambiarne rapidamente il colore.",
"description_touch": "Crea, rinomina e personalizza i tuoi calendari.",
"name": "Nome",
"name_placeholder": "Nome del calendario",
"color": "Colore",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "カレンダー管理",
"description": "カレンダーの作成、名前変更、カスタマイズができます。サイドバーのカレンダーを右クリックして色を素早く変更できます。",
"description_touch": "カレンダーの作成、名前変更、カスタマイズができます。",
"name": "名前",
"name_placeholder": "カレンダー名",
"color": "色",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "캘린더 관리",
"description": "캘린더를 만들고 이름을 바꾸거나 색상을 꾸며보세요. 사이드바에서 캘린더를 우클릭하면 색상을 빠르게 바꿀 수 있어요.",
"description_touch": "캘린더를 만들고 이름을 바꾸거나 색상을 꾸며보세요.",
"name": "이름",
"name_placeholder": "캘린더 이름",
"color": "색상",
+1
View File
@@ -3991,6 +3991,7 @@
"management": {
"title": "Kalendāru pārvaldība",
"description": "Izveidojiet, pārdēvējiet un konfigurējiet kalendārus. Ar labo klikšķi varat mainīt kalendāra krāsu.",
"description_touch": "Izveidojiet, pārdēvējiet un konfigurējiet kalendārus.",
"name": "Nosaukums",
"name_placeholder": "Kalendāra nosaukums",
"color": "Krāsa",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Календарийн менежмент",
"description": "Хуанлигаа үүсгэх, нэрийг нь өөрчлөх, өөрчлөх. Хажуугийн самбар дээрх хуанли дээр хулганы баруун товчийг дарж өнгийг нь хурдан солино.",
"description_touch": "Хуанлигаа үүсгэх, нэрийг нь өөрчлөх, тохируулах.",
"name": "Нэр",
"name_placeholder": "Календарийн нэр",
"color": "Өнгө",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Administrer kalendere",
"description": "Opprett, gi nytt navn og tilpass kalenderne dine. Høyreklikk på en kalender i sidefeltet for raskt å endre fargen.",
"description_touch": "Opprett, gi nytt navn og tilpass kalenderne dine.",
"name": "Navn",
"name_placeholder": "Kalendernavn",
"color": "Farge",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Agendabeheer",
"description": "Maak, hernoem en pas uw agenda's aan. Klik met de rechtermuisknop op een agenda in de zijbalk om snel de kleur te wijzigen.",
"description_touch": "Maak, hernoem en pas uw agenda's aan.",
"name": "Naam",
"name_placeholder": "Agendanaam",
"color": "Kleur",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Zarządzanie kalendarzem",
"description": "Twórz, zmieniaj nazwy i dostosowuj swoje kalendarze. Kliknij prawym przyciskiem kalendarz na pasku bocznym, aby szybko zmienić jego kolor.",
"description_touch": "Twórz, zmieniaj nazwy i dostosowuj swoje kalendarze.",
"name": "Nazwa",
"name_placeholder": "Nazwa kalendarza",
"color": "Kolor",
+1
View File
@@ -4018,6 +4018,7 @@
"management": {
"title": "Gerenciamento de calendários",
"description": "Crie, renomeie e personalize seus calendários. Clique com o botão direito em um calendário na barra lateral para alterar rapidamente sua cor.",
"description_touch": "Crie, renomeie e personalize seus calendários.",
"name": "Nome",
"name_placeholder": "Nome do calendário",
"color": "Cor",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Gestionarea calendarului",
"description": "Creați, redenumiți și personalizați-vă calendarele. Faceți clic dreapta pe un calendar din bara laterală pentru a-i schimba rapid culoarea.",
"description_touch": "Creați, redenumiți și personalizați-vă calendarele.",
"name": "Nume",
"name_placeholder": "Numele calendarului",
"color": "Culoare",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Управление календарями",
"description": "Создавайте, переименовывайте и настраивайте свои календари. Щёлкните правой кнопкой мыши на календаре в боковой панели для быстрой смены цвета.",
"description_touch": "Создавайте, переименовывайте и настраивайте свои календари.",
"name": "Имя",
"name_placeholder": "Название календаря",
"color": "Цвет",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "Správa kalendára",
"description": "Vytvárajte, premenúvajte a prispôsobujte si svoje kalendáre.",
"description_touch": "Vytvárajte, premenúvajte a prispôsobujte si svoje kalendáre.",
"name": "Názov",
"name_placeholder": "Názov kalendára",
"color": "Farba",
+1
View File
@@ -3136,6 +3136,7 @@
"management": {
"title": "การจัดการปฏิทิน",
"description": "สร้าง เปลี่ยนชื่อ และปรับแต่งปฏิทินของคุณ คลิกขวาที่ปฏิทินในแถบด้านข้างเพื่อเปลี่ยนสีได้อย่างรวดเร็ว",
"description_touch": "สร้าง เปลี่ยนชื่อ และปรับแต่งปฏิทินของคุณ",
"name": "ชื่อ",
"name_placeholder": "ชื่อปฏิทิน",
"color": "สี",
+1
View File
@@ -4018,6 +4018,7 @@
"management": {
"title": "Takvim Yönetimi",
"description": "Takvimlerinizi oluşturun, yeniden adlandırın ve özelleştirin. Rengini hızlıca değiştirmek için kenar çubuğundaki bir takvime sağ tıklayın.",
"description_touch": "Takvimlerinizi oluşturun, yeniden adlandırın ve özelleştirin.",
"name": "Ad",
"name_placeholder": "Takvim adı",
"color": "Renk",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "Управління календарем",
"description": "Створюйте, перейменовуйте та налаштовуйте свої календарі. Клацніть правою кнопкою миші календар на бічній панелі, щоб швидко змінити його колір.",
"description_touch": "Створюйте, перейменовуйте та налаштовуйте свої календарі.",
"name": "Ім'я",
"name_placeholder": "Назва календаря",
"color": "колір",
+1
View File
@@ -4021,6 +4021,7 @@
"management": {
"title": "行事曆管理",
"description": "建立、重新命名與自訂您的行事曆。在側邊欄的行事曆上按一下滑鼠右鍵,可快速變更顏色。",
"description_touch": "建立、重新命名與自訂您的行事曆。",
"name": "名稱",
"name_placeholder": "行事曆名稱",
"color": "顏色",
+1
View File
@@ -3992,6 +3992,7 @@
"management": {
"title": "日历管理",
"description": "创建、重命名和自定义您的日历。右键单击侧栏中的日历可快速更改其颜色。",
"description_touch": "创建、重命名和自定义您的日历。",
"name": "名称",
"name_placeholder": "日历名称",
"color": "颜色",