intermittent commit

This commit is contained in:
2026-07-14 13:22:10 +02:00
parent 8aa1943581
commit e6f7c45f0a
76 changed files with 6039 additions and 2188 deletions

View File

@@ -1,4 +1,5 @@
import { i18nMessage } from "../../i18n/LanguageContext";import { useEffect, useId, useMemo, useRef, useState } from "react";
import { i18nMessage, usePlatformLanguage } from "../../i18n/LanguageContext";
import { useEffect, useId, useMemo, useRef, useState } from "react";
import type { CSSProperties, KeyboardEvent } from "react";
import { createPortal } from "react-dom";
import Button from "../Button";
@@ -15,6 +16,7 @@ type EmailAddressInputProps = {
value: MailboxAddress[];
onChange?: (value: MailboxAddress[]) => void;
onAddressAdded?: (address: MailboxAddress) => void;
onSuggestionQueryChange?: (query: string) => void;
suggestions?: MailboxAddress[];
allowMultiple?: boolean;
clearOnAdd?: boolean;
@@ -31,6 +33,7 @@ export default function EmailAddressInput({
value,
onChange,
onAddressAdded,
onSuggestionQueryChange,
suggestions = [],
allowMultiple = true,
clearOnAdd = false,
@@ -42,6 +45,7 @@ export default function EmailAddressInput({
compact = false,
showAddButton
}: EmailAddressInputProps) {
const { translateText } = usePlatformLanguage();
const inputId = useId();
const normalizedValue = useMemo(() => dedupeAddresses(value), [value]);
const normalizedSuggestions = useMemo(() => dedupeAddresses(suggestions), [suggestions]);
@@ -51,9 +55,18 @@ export default function EmailAddressInput({
const [dialogEmail, setDialogEmail] = useState("");
const [error, setError] = useState("");
const [popoverStyle, setPopoverStyle] = useState<CSSProperties>({});
const lastSuggestionQueryRef = useRef<string | null>(null);
const addButtonRef = useRef<HTMLButtonElement | null>(null);
const canUseAddButton = showAddButton ?? allowMultiple;
useEffect(() => {
const query = entryText.trim();
if (query === "" && lastSuggestionQueryRef.current === null) return;
if (query === lastSuggestionQueryRef.current) return;
lastSuggestionQueryRef.current = query;
onSuggestionQueryChange?.(query);
}, [entryText, onSuggestionQueryChange]);
const filteredSuggestions = useMemo(() => {
const query = entryText.trim().toLowerCase();
if (!query) return normalizedSuggestions.slice(0, 6);
@@ -168,18 +181,18 @@ export default function EmailAddressInput({
if (event.key === "Escape") setDialogOpen(false);
}}>
<h4 id={`${inputId}-dialog-title`}>i18n:govoplan-core.add_address.a71075c4</h4>
<h4 id={`${inputId}-dialog-title`}>{translateText("i18n:govoplan-core.add_address.a71075c4")}</h4>
<label>
<span>i18n:govoplan-core.name.709a2322</span>
<input value={dialogName} onChange={(event) => setDialogName(event.target.value)} placeholder={namePlaceholder} autoFocus />
<span>{translateText("i18n:govoplan-core.name.709a2322")}</span>
<input value={dialogName} onChange={(event) => setDialogName(event.target.value)} placeholder={translateText(namePlaceholder)} autoFocus />
</label>
<label>
<span>i18n:govoplan-core.email_address.c94d3175</span>
<span>{translateText("i18n:govoplan-core.email_address.c94d3175")}</span>
<input value={dialogEmail} onChange={(event) => setDialogEmail(event.target.value)} placeholder={emailPlaceholder} inputMode="email" />
</label>
<div className="button-row compact-actions">
<Button type="button" onClick={() => setDialogOpen(false)}>i18n:govoplan-core.cancel.77dfd213</Button>
<Button type="button" variant="primary" onClick={commitDialogAddress}>{addLabel}</Button>
<Button type="button" onClick={() => setDialogOpen(false)}>{translateText("i18n:govoplan-core.cancel.77dfd213")}</Button>
<Button type="button" variant="primary" onClick={commitDialogAddress}>{translateText(addLabel)}</Button>
</div>
</div>,
document.body
@@ -189,11 +202,11 @@ export default function EmailAddressInput({
<div className={`email-address-input ${compact ? "compact" : ""} ${disabled ? "disabled" : ""} ${canUseAddButton ? "has-add-button" : ""}`}>
<div className={`email-address-editor ${error ? "has-error" : ""}`}>
<div className="email-chip-list" aria-live="polite">
{normalizedValue.length === 0 && !entryText && <span className="email-chip-empty">{emptyText}</span>}
{normalizedValue.length === 0 && !entryText && <span className="email-chip-empty">{translateText(emptyText)}</span>}
{normalizedValue.map((address) => {
const valid = isValidEmailAddress(address.email);
return (
<span className={`email-chip ${valid ? "" : "invalid"}`} key={address.email} title={valid ? address.email : "i18n:govoplan-core.invalid_email_address.9e4ee6d7"}>
<span className={`email-chip ${valid ? "" : "invalid"}`} key={address.email} title={valid ? address.email : translateText("i18n:govoplan-core.invalid_email_address.9e4ee6d7")}>
<span className="email-chip-main">{addressDisplayName(address)}</span>
{address.name && <span className="email-chip-address">{address.email}</span>}
{!disabled &&
@@ -217,11 +230,11 @@ export default function EmailAddressInput({
setError("");
}}
onKeyDown={handleTextKeyDown}
placeholder={i18nMessage("i18n:govoplan-core.value_value.27085f09", { value0: namePlaceholder, value1: emailPlaceholder })}
aria-label="i18n:govoplan-core.type_a_name_and_email_address_then_press_enter.7c8d43f0" />
placeholder={translateText(i18nMessage("i18n:govoplan-core.value_value.27085f09", { value0: translateText(namePlaceholder), value1: emailPlaceholder }))}
aria-label={translateText("i18n:govoplan-core.type_a_name_and_email_address_then_press_enter.7c8d43f0")} />
{canUseAddButton &&
<button ref={addButtonRef} type="button" className="email-address-plus" aria-label="i18n:govoplan-core.open_address_form.f8ee560f" title="i18n:govoplan-core.add_address_with_form.13b3b3e7" onClick={() => setDialogOpen((open) => !open)}>
<button ref={addButtonRef} type="button" className="email-address-plus" aria-label={translateText("i18n:govoplan-core.open_address_form.f8ee560f")} title={translateText("i18n:govoplan-core.add_address_with_form.13b3b3e7")} onClick={() => setDialogOpen((open) => !open)}>
+
</button>
}
@@ -230,7 +243,7 @@ export default function EmailAddressInput({
</div>
{!disabled && filteredSuggestions.length > 0 && entryText.trim() &&
<div className="email-address-suggestions" role="listbox" aria-label="i18n:govoplan-core.address_suggestions.45ba4a20">
<div className="email-address-suggestions" role="listbox" aria-label={translateText("i18n:govoplan-core.address_suggestions.45ba4a20")}>
{filteredSuggestions.map((item) =>
<button type="button" key={item.email} onClick={() => applySuggestion(item)} role="option">
<span>{addressDisplayName(item)}</span>
@@ -239,7 +252,7 @@ export default function EmailAddressInput({
)}
</div>
}
{error && <p className="form-help danger-text">{error}</p>}
{error && <p className="form-help danger-text">{translateText(error)}</p>}
{addressDialog}
</div>);