From a1bd39f68449ae9783488f5db068388afef65ade Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 4 Nov 2026 10:55:58 +0100 Subject: [PATCH] fix: shorten the calendar search placeholder when the field is narrow --- ...alendar-search-placeholder-ux-pim.test.tsx | 53 +++++++++++++++++++ components/calendar/calendar-search.tsx | 16 ++++-- locales/ar/common.json | 5 ++ locales/ca/common.json | 5 ++ locales/cs/common.json | 5 ++ locales/da/common.json | 5 ++ locales/de/common.json | 5 ++ locales/en/common.json | 5 ++ locales/es/common.json | 5 ++ locales/et/common.json | 5 ++ locales/fa/common.json | 5 ++ locales/fr/common.json | 5 ++ locales/he/common.json | 5 ++ locales/hu/common.json | 5 ++ locales/it/common.json | 5 ++ locales/ja/common.json | 5 ++ locales/ko/common.json | 5 ++ locales/lv/common.json | 5 ++ locales/mn/common.json | 5 ++ locales/nb/common.json | 5 ++ locales/nl/common.json | 5 ++ locales/pl/common.json | 5 ++ locales/pt/common.json | 5 ++ locales/ro/common.json | 5 ++ locales/ru/common.json | 5 ++ locales/sk/common.json | 5 ++ locales/th/common.json | 5 ++ locales/tr/common.json | 5 ++ locales/uk/common.json | 5 ++ locales/vi/common.json | 5 ++ locales/zh-TW/common.json | 5 ++ locales/zh/common.json | 5 ++ 32 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 components/calendar/__tests__/calendar-search-placeholder-ux-pim.test.tsx diff --git a/components/calendar/__tests__/calendar-search-placeholder-ux-pim.test.tsx b/components/calendar/__tests__/calendar-search-placeholder-ux-pim.test.tsx new file mode 100644 index 000000000..09b877bfa --- /dev/null +++ b/components/calendar/__tests__/calendar-search-placeholder-ux-pim.test.tsx @@ -0,0 +1,53 @@ +import { render, screen, cleanup } from '@testing-library/react'; +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { createRef } from 'react'; +import { CalendarSearchField } from '../calendar-search'; + +// pim-21: at 150 % in German the search box read "Termine durchs". A field +// too narrow for "Search events" now says "Search"; its name stays whole. + +/** The field is `width` px wide inside; text measures 10px per character. */ +function stubWidth(width: number) { + vi.stubGlobal('ResizeObserver', class implements ResizeObserver { + constructor(private readonly callback: ResizeObserverCallback) {} + observe(target: Element) { + this.callback([{ target, contentRect: { width } } as unknown as ResizeObserverEntry], this); + } + unobserve() {} + disconnect() {} + }); + vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({ + font: '', + measureText: (text: string) => ({ width: text.length * 10 }), + } as unknown as CanvasRenderingContext2D); +} + +afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); + vi.restoreAllMocks(); +}); + +describe('CalendarSearchField placeholder', () => { + it('says the short word in a narrow field', () => { + // "search.title" is 12 characters: 120px does not fit in 96. + stubWidth(96); + render(); + const input = screen.getByRole('searchbox'); + expect(input.getAttribute('placeholder')).toBe('search_short'); + expect(input.getAttribute('aria-label')).toBe('search.title'); + }); + + it('keeps the whole phrase where it fits', () => { + stubWidth(200); + render(); + expect(screen.getByRole('searchbox').getAttribute('placeholder')).toBe('search.title'); + }); + + it('still hands the input to the page', () => { + stubWidth(200); + const ref = createRef(); + render(); + expect(ref.current).toBe(screen.getByRole('searchbox')); + }); +}); diff --git a/components/calendar/calendar-search.tsx b/components/calendar/calendar-search.tsx index 306d427ab..44c1d168b 100644 --- a/components/calendar/calendar-search.tsx +++ b/components/calendar/calendar-search.tsx @@ -1,6 +1,6 @@ "use client"; -import { forwardRef, useMemo, useRef, type KeyboardEvent } from "react"; +import { forwardRef, useCallback, useMemo, useRef, type KeyboardEvent } from "react"; import { useTranslations } from "next-intl"; import { format } from "date-fns"; import { Search, MapPin, Repeat } from "@/components/icons"; @@ -18,6 +18,7 @@ import type { Calendar, CalendarEvent } from "@/lib/jmap/types"; import type { CalendarSearchResults as Results } from "@/hooks/use-calendar-event-search"; import { getEventColor } from "./event-card"; import { useCvdColor } from "@/hooks/use-cvd-color"; +import { useShortenedText } from "@/hooks/use-shortened-text"; const RESULT_SELECTOR = "[data-search-result]"; @@ -34,6 +35,15 @@ interface CalendarSearchFieldProps { export const CalendarSearchField = forwardRef( function CalendarSearchField({ value, onChange, onDismiss, autoFocus, className }, ref) { const t = useTranslations("calendar"); + const tGrid = useTranslations("ux_pim_grid"); + // A narrow field says "Search" rather than cut "Search events" (pim-21: + // "Termine durchs" at 150 % in German). The name stays the full one. + const [measureRef, placeholder] = useShortenedText([t("search.title"), tGrid("search_short")]); + const inputRef = useCallback((node: HTMLInputElement | null) => { + measureRef(node); + if (typeof ref === "function") ref(node); + else if (ref) ref.current = node; + }, [measureRef, ref]); const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { // Escape clears the search first, then leaves the field (R8). @@ -49,7 +59,7 @@ export const CalendarSearchField = forwardRef onChange(e.target.value)} @@ -57,7 +67,7 @@ export const CalendarSearchField = forwardRef onChange("")} clearLabel={t("search.clear")} shortcut="/" - placeholder={t("search.title")} + placeholder={placeholder} aria-label={t("search.title")} autoComplete="off" data-calendar-search diff --git a/locales/ar/common.json b/locales/ar/common.json index f8800991e..387b49d32 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "المؤسسة" }, + "ux_pim_grid": { + "search_short": "بحث", + "reply": "رد", + "reply_to_invitation": "الرد على الدعوة" + }, "vacation_calendar": { "label": "حجز تقويمي", "description": "يضيف حدثًا مشغولًا لمدة غيابك، حتى لا يحجز أحد اجتماعات معك خلالها.", diff --git a/locales/ca/common.json b/locales/ca/common.json index 62c75d062..18d1b645a 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Organització" }, + "ux_pim_grid": { + "search_short": "Cerca", + "reply": "Respon", + "reply_to_invitation": "Respon a la invitació" + }, "vacation_calendar": { "label": "Bloca el meu calendari", "description": "Afegeix un esdeveniment ocupat mentre sou fora, perquè ningú us convoqui a reunions aleshores.", diff --git a/locales/cs/common.json b/locales/cs/common.json index cd765731a..36c2ef2d2 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organizace" }, + "ux_pim_grid": { + "search_short": "Hledat", + "reply": "Odpovědět", + "reply_to_invitation": "Odpovědět na pozvánku" + }, "vacation_calendar": { "label": "Zablokovat můj kalendář", "description": "Přidá obsazenou událost na dobu vaší nepřítomnosti, aby vám tehdy nikdo neplánoval schůzky.", diff --git a/locales/da/common.json b/locales/da/common.json index 68c3ace47..00147ee7e 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Organisation" }, + "ux_pim_grid": { + "search_short": "Søg", + "reply": "Svar", + "reply_to_invitation": "Svar på invitationen" + }, "vacation_calendar": { "label": "Bloker min kalender", "description": "Tilføjer en optaget begivenhed, mens du er væk, så ingen booker møder med dig imens.", diff --git a/locales/de/common.json b/locales/de/common.json index 6acbe0328..4c186bf61 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organisation" }, + "ux_pim_grid": { + "search_short": "Suchen", + "reply": "Antworten", + "reply_to_invitation": "Einladung beantworten" + }, "vacation_calendar": { "label": "Meinen Kalender blockieren", "description": "Trägt für Ihre Abwesenheit einen belegten Termin ein, damit Ihnen niemand Besprechungen in dieser Zeit einträgt.", diff --git a/locales/en/common.json b/locales/en/common.json index 9fe28226c..cf9e21b43 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Organisation" }, + "ux_pim_grid": { + "search_short": "Search", + "reply": "Reply", + "reply_to_invitation": "Answer the invitation" + }, "vacation_calendar": { "label": "Block my calendar", "description": "Adds a busy event for the time you’re away, so nobody books meetings with you then.", diff --git a/locales/es/common.json b/locales/es/common.json index e1f5a5ebe..30753a99c 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organización" }, + "ux_pim_grid": { + "search_short": "Buscar", + "reply": "Responder", + "reply_to_invitation": "Responder a la invitación" + }, "vacation_calendar": { "label": "Bloquear mi calendario", "description": "Añade un evento ocupado mientras estás fuera, para que nadie te convoque a reuniones entonces.", diff --git a/locales/et/common.json b/locales/et/common.json index f77713d19..a2a08af3d 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Organisatsioon" }, + "ux_pim_grid": { + "search_short": "Otsi", + "reply": "Vasta", + "reply_to_invitation": "Vasta kutsele" + }, "vacation_calendar": { "label": "Blokeeri minu kalender", "description": "Lisab äraoleku ajaks hõivatud sündmuse, et keegi sel ajal sulle koosolekuid ei paneks.", diff --git a/locales/fa/common.json b/locales/fa/common.json index 36f5febbd..9b39e60dd 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "سازمان" }, + "ux_pim_grid": { + "search_short": "جستجو", + "reply": "پاسخ", + "reply_to_invitation": "پاسخ به دعوت" + }, "vacation_calendar": { "label": "مسدود کردن تقویم من", "description": "برای مدت غیبت شما یک رویداد مشغول اضافه می‌کند تا کسی در آن زمان جلسه نگذارد.", diff --git a/locales/fr/common.json b/locales/fr/common.json index 96ad6b352..ec5279991 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organisation" }, + "ux_pim_grid": { + "search_short": "Rechercher", + "reply": "Répondre", + "reply_to_invitation": "Répondre à l’invitation" + }, "vacation_calendar": { "label": "Bloquer mon calendrier", "description": "Ajoute un événement occupé pendant votre absence, pour que personne ne vous invite à une réunion à ce moment-là.", diff --git a/locales/he/common.json b/locales/he/common.json index 8190e738d..1ab4a6238 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -5551,6 +5551,11 @@ "company_calendars": { "section": "ארגון" }, + "ux_pim_grid": { + "search_short": "חיפוש", + "reply": "מענה", + "reply_to_invitation": "מענה להזמנה" + }, "vacation_calendar": { "label": "חסום את היומן שלי", "description": "מוסיף אירוע תפוס לזמן ההיעדרות, כדי שאף אחד לא יקבע איתך פגישות אז.", diff --git a/locales/hu/common.json b/locales/hu/common.json index 9d7abd079..914c17671 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Szervezet" }, + "ux_pim_grid": { + "search_short": "Keresés", + "reply": "Válasz", + "reply_to_invitation": "Válasz a meghívásra" + }, "vacation_calendar": { "label": "Naptáram foglalása", "description": "Foglalt eseményt tesz a távollét idejére, hogy addig senki ne szervezzen Önnel megbeszélést.", diff --git a/locales/it/common.json b/locales/it/common.json index c230dd475..621a7cf13 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organizzazione" }, + "ux_pim_grid": { + "search_short": "Cerca", + "reply": "Rispondi", + "reply_to_invitation": "Rispondi all’invito" + }, "vacation_calendar": { "label": "Blocca il mio calendario", "description": "Aggiunge un evento occupato per il periodo di assenza, così nessuno ti fissa riunioni in quei giorni.", diff --git a/locales/ja/common.json b/locales/ja/common.json index 859b086d6..a0daf6e38 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "組織" }, + "ux_pim_grid": { + "search_short": "検索", + "reply": "返信", + "reply_to_invitation": "招待に返信" + }, "vacation_calendar": { "label": "カレンダーをブロック", "description": "不在の期間に予定ありの予定を追加し、その間に会議を入れられないようにします。", diff --git a/locales/ko/common.json b/locales/ko/common.json index b26122750..ca882fba2 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "조직" }, + "ux_pim_grid": { + "search_short": "검색", + "reply": "응답", + "reply_to_invitation": "초대에 응답" + }, "vacation_calendar": { "label": "내 캘린더 차단", "description": "부재 기간에 바쁨 일정을 추가해 그동안 회의가 잡히지 않게 합니다.", diff --git a/locales/lv/common.json b/locales/lv/common.json index 6474a45eb..a94e68744 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organizācija" }, + "ux_pim_grid": { + "search_short": "Meklēt", + "reply": "Atbildēt", + "reply_to_invitation": "Atbildēt uz ielūgumu" + }, "vacation_calendar": { "label": "Bloķēt manu kalendāru", "description": "Pievieno aizņemtu notikumu prombūtnes laikam, lai tad neviens neplānotu ar jums sapulces.", diff --git a/locales/mn/common.json b/locales/mn/common.json index 49f47d310..ce0cd5602 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Байгууллага" }, + "ux_pim_grid": { + "search_short": "Хайх", + "reply": "Хариулах", + "reply_to_invitation": "Урилгад хариулах" + }, "vacation_calendar": { "label": "Миний хуанлийг хаах", "description": "Таныг байхгүй үед завгүй арга хэмжээ нэмж, тэр үед хэн ч уулзалт товлохгүй болгоно.", diff --git a/locales/nb/common.json b/locales/nb/common.json index 6c54c14c6..d1ed21f2d 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Organisasjon" }, + "ux_pim_grid": { + "search_short": "Søk", + "reply": "Svar", + "reply_to_invitation": "Svar på invitasjonen" + }, "vacation_calendar": { "label": "Blokker kalenderen min", "description": "Legger inn en opptatt-hendelse mens du er borte, så ingen booker møter med deg da.", diff --git a/locales/nl/common.json b/locales/nl/common.json index caa6592d6..4ed0f4dba 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organisatie" }, + "ux_pim_grid": { + "search_short": "Zoeken", + "reply": "Reageren", + "reply_to_invitation": "Op uitnodiging reageren" + }, "vacation_calendar": { "label": "Mijn agenda blokkeren", "description": "Zet een bezette afspraak in je agenda voor de tijd dat je weg bent, zodat niemand dan vergaderingen met je plant.", diff --git a/locales/pl/common.json b/locales/pl/common.json index a42440af6..633c4f8e6 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organizacja" }, + "ux_pim_grid": { + "search_short": "Szukaj", + "reply": "Odpowiedz", + "reply_to_invitation": "Odpowiedz na zaproszenie" + }, "vacation_calendar": { "label": "Zablokuj mój kalendarz", "description": "Dodaje zajęte wydarzenie na czas Twojej nieobecności, żeby nikt nie umawiał wtedy z Tobą spotkań.", diff --git a/locales/pt/common.json b/locales/pt/common.json index bd73fa852..a14a69a17 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Organização" }, + "ux_pim_grid": { + "search_short": "Pesquisar", + "reply": "Responder", + "reply_to_invitation": "Responder ao convite" + }, "vacation_calendar": { "label": "Bloquear o meu calendário", "description": "Adiciona um evento ocupado durante a sua ausência, para que ninguém marque reuniões consigo nessa altura.", diff --git a/locales/ro/common.json b/locales/ro/common.json index ad9938c1a..dc5b51fd1 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Organizație" }, + "ux_pim_grid": { + "search_short": "Caută", + "reply": "Răspunde", + "reply_to_invitation": "Răspunde la invitație" + }, "vacation_calendar": { "label": "Blochează-mi calendarul", "description": "Adaugă un eveniment ocupat pe durata absenței, ca nimeni să nu vă programeze ședințe atunci.", diff --git a/locales/ru/common.json b/locales/ru/common.json index e32676136..f580285c7 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Организация" }, + "ux_pim_grid": { + "search_short": "Поиск", + "reply": "Ответить", + "reply_to_invitation": "Ответить на приглашение" + }, "vacation_calendar": { "label": "Занять мой календарь", "description": "Добавляет событие «занят» на время отсутствия, чтобы вам не назначали встречи.", diff --git a/locales/sk/common.json b/locales/sk/common.json index 3da1ed6c4..0ae0751b2 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Organizácia" }, + "ux_pim_grid": { + "search_short": "Hľadať", + "reply": "Odpovedať", + "reply_to_invitation": "Odpovedať na pozvánku" + }, "vacation_calendar": { "label": "Zablokovať môj kalendár", "description": "Pridá obsadenú udalosť na čas vašej neprítomnosti, aby vám vtedy nikto neplánoval stretnutia.", diff --git a/locales/th/common.json b/locales/th/common.json index d8fbce3f9..4cb41ecbd 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -2736,6 +2736,11 @@ "company_calendars": { "section": "องค์กร" }, + "ux_pim_grid": { + "search_short": "ค้นหา", + "reply": "ตอบกลับ", + "reply_to_invitation": "ตอบกลับคำเชิญ" + }, "vacation_calendar": { "label": "บล็อกปฏิทินของฉัน", "description": "เพิ่มกิจกรรมไม่ว่างในช่วงที่คุณไม่อยู่ เพื่อไม่ให้ใครนัดประชุมกับคุณในเวลานั้น", diff --git a/locales/tr/common.json b/locales/tr/common.json index 59e4b73db..2c534597e 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "Kuruluş" }, + "ux_pim_grid": { + "search_short": "Ara", + "reply": "Yanıtla", + "reply_to_invitation": "Davete yanıt ver" + }, "vacation_calendar": { "label": "Takvimimi kapat", "description": "Uzakta olduğunuz süre için meşgul bir etkinlik ekler, böylece o sırada kimse sizinle toplantı ayarlamaz.", diff --git a/locales/uk/common.json b/locales/uk/common.json index be354b49c..078b69b26 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "Організація" }, + "ux_pim_grid": { + "search_short": "Пошук", + "reply": "Відповісти", + "reply_to_invitation": "Відповісти на запрошення" + }, "vacation_calendar": { "label": "Заблокувати мій календар", "description": "Додає подію «зайнято» на час вашої відсутності, щоб вам тоді не призначали зустрічей.", diff --git a/locales/vi/common.json b/locales/vi/common.json index fe1be93e4..6c5474904 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -5153,6 +5153,11 @@ "company_calendars": { "section": "Tổ chức" }, + "ux_pim_grid": { + "search_short": "Tìm", + "reply": "Trả lời", + "reply_to_invitation": "Trả lời lời mời" + }, "vacation_calendar": { "label": "Chặn lịch của tôi", "description": "Thêm một sự kiện bận trong thời gian bạn vắng mặt để không ai đặt cuộc họp với bạn lúc đó.", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 205e145f6..19437aba7 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -5141,6 +5141,11 @@ "company_calendars": { "section": "組織" }, + "ux_pim_grid": { + "search_short": "搜尋", + "reply": "回覆", + "reply_to_invitation": "回覆邀請" + }, "vacation_calendar": { "label": "佔用我的行事曆", "description": "在你外出期間新增一個忙碌活動,這樣別人不會在這段時間約你開會。", diff --git a/locales/zh/common.json b/locales/zh/common.json index 0e601ed20..7e0fff333 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -5732,6 +5732,11 @@ "company_calendars": { "section": "组织" }, + "ux_pim_grid": { + "search_short": "搜索", + "reply": "回复", + "reply_to_invitation": "回复邀请" + }, "vacation_calendar": { "label": "占用我的日历", "description": "在你外出期间添加一个忙碌活动,这样别人不会在这段时间约你开会。",