fix(quick-260921-pi9): Grossansicht per Portal, "1 Minute" statt "1 Minuten", Kachel-Vorgabe 8x12
Befunde aus dem Browser-Rundgang am 21.09.2026:
- Die Grossansicht lag in einem `react-grid-item` mit CSS-`transform`; ein
transformierter Vorfahr wird fuer `position: fixed` zum Bezugsrahmen, der
Dialog war deshalb auf die Kachelflaeche (531x216) beschraenkt statt den
Viewport zu fuellen. Jetzt per `createPortal` in `document.body`, wie der
Kalender-Tooltip.
- Wechselintervall "1 Minuten" -> ICU-Plural (`one {# Minute}`), de + en; der
Formular-Test nutzt dafuer den echten `createTranslator` von next-intl auf
der echten de.json statt eines `{n}`-Ersatzes.
- Standardgroesse 8x8 (216 px hoch) war zu flach fuer ein Foto -> 8x12 wie die
Kalender-Vorgabe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,7 @@ describe('WIDGET_CONSTRAINTS (DASH-11)', () => {
|
||||
calculator: { minW: 3, minH: 10, defaultW: 6, defaultH: 10 },
|
||||
favorites: { minW: 3, minH: 3, defaultW: 6, defaultH: 10 },
|
||||
stopwatch: { minW: 4, minH: 3, defaultW: 6, defaultH: 6 },
|
||||
'picture-frame': { minW: 4, minH: 4, defaultW: 8, defaultH: 8 },
|
||||
'picture-frame': { minW: 4, minH: 4, defaultW: 8, defaultH: 12 },
|
||||
});
|
||||
|
||||
let counted = 0;
|
||||
|
||||
@@ -55,10 +55,11 @@ export const WIDGET_CONSTRAINTS: Record<
|
||||
calculator: { minW: 3, minH: 10, defaultW: 6, defaultH: 10 },
|
||||
favorites: { minW: 3, minH: 3, defaultW: 6, defaultH: 10 },
|
||||
stopwatch: { minW: 4, minH: 3, defaultW: 6, defaultH: 6 },
|
||||
// quick-260921-pi9: ein Bild braucht Flaeche — 8x8 ist ein Quadrat von der
|
||||
// Groesse einer halben Notiz (6x8 -> 8x8), 4x4 die kleinste Kachel, in der
|
||||
// Bild und Unterschrift-Streifen noch erkennbar sind.
|
||||
'picture-frame': { minW: 4, minH: 4, defaultW: 8, defaultH: 8 },
|
||||
// quick-260921-pi9: ein Bild braucht Flaeche — 8x12 entspricht der
|
||||
// Kalender-Vorgabe (rund 530x330 px, Querformat wie ein Foto); 8x8 war im
|
||||
// Browser-Rundgang mit 216 px Hoehe zu flach. 4x4 ist die kleinste Kachel,
|
||||
// in der Bild und Unterschrift-Streifen noch erkennbar sind.
|
||||
'picture-frame': { minW: 4, minH: 4, defaultW: 8, defaultH: 12 },
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface PictureFrameLightboxProps {
|
||||
@@ -21,6 +22,12 @@ interface PictureFrameLightboxProps {
|
||||
* `referrerPolicy="no-referrer"` auch hier (T-PI9-09): ein Fremdhost erfaehrt
|
||||
* die Tessera-Adresse nicht. Die Unterschrift ist ein React-Textknoten,
|
||||
* kein HTML (T-PI9-06).
|
||||
*
|
||||
* Gerendert per `createPortal` in `document.body` (wie der Kalender-Tooltip):
|
||||
* die Kachel liegt in einem `react-grid-item` mit CSS-`transform`, und ein
|
||||
* transformierter Vorfahr macht sich fuer `position: fixed` zum Bezugsrahmen —
|
||||
* ohne Portal waere die Grossansicht auf die Kachelflaeche beschraenkt
|
||||
* (im Browser-Rundgang am 21.09.2026 so gesehen: Dialog 531x216 statt Viewport).
|
||||
*/
|
||||
export function PictureFrameLightbox({ src, caption, onClose }: PictureFrameLightboxProps) {
|
||||
const t = useTranslations('widgets');
|
||||
@@ -38,7 +45,9 @@ export function PictureFrameLightbox({ src, caption, onClose }: PictureFrameLigh
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
if (typeof document === 'undefined') return null;
|
||||
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<button
|
||||
type="button"
|
||||
@@ -93,6 +102,7 @@ export function PictureFrameLightbox({ src, caption, onClose }: PictureFrameLigh
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,19 +8,14 @@ import de from '@/messages/de.json';
|
||||
* Eintragsliste mit Vorschau/Unterschrift/Pfeilen/Entfernen, Upload-Kette,
|
||||
* https-Pruefung, Grenze 30.
|
||||
*/
|
||||
vi.mock('next-intl', async () => {
|
||||
const messages = (await import('@/messages/de.json')).default as Record<string, unknown>;
|
||||
const lookup = (path: string): string | undefined =>
|
||||
path.split('.').reduce<unknown>((o, k) => (o && typeof o === 'object' ? (o as Record<string, unknown>)[k] : undefined), messages) as
|
||||
| string
|
||||
| undefined;
|
||||
vi.mock('next-intl', async (importOriginal) => {
|
||||
// Echter ICU-Uebersetzer von next-intl auf der echten de.json: die
|
||||
// Intervall-Beschriftung nutzt `{n, plural, one {…} other {…}}`, ein
|
||||
// handgeschriebener `{n}`-Ersatz wuerde das Muster nicht verstehen.
|
||||
const actual = await importOriginal<typeof import('next-intl')>();
|
||||
const messages = (await import('@/messages/de.json')).default;
|
||||
return {
|
||||
useTranslations:
|
||||
(ns?: string) =>
|
||||
(key: string, values?: Record<string, unknown>) => {
|
||||
const raw = lookup(ns ? `${ns}.${key}` : key) ?? key;
|
||||
return values ? raw.replace(/\{(\w+)\}/g, (_: string, n: string) => String(values[n] ?? '')) : raw;
|
||||
},
|
||||
useTranslations: (ns?: string) => actual.createTranslator({ locale: 'de', messages, namespace: ns }),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -70,6 +65,7 @@ describe('PictureFrameConfigForm (quick-260921-pi9)', () => {
|
||||
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('Kein Wechsel');
|
||||
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('5 Sekunden');
|
||||
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('30 Minuten');
|
||||
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('1 Minute');
|
||||
|
||||
fireEvent.change(fit, { target: { value: 'cover' } });
|
||||
expect(onChange).toHaveBeenLastCalledWith({ fit: 'cover' });
|
||||
|
||||
@@ -347,7 +347,7 @@
|
||||
"intervalLabel": "Wechselintervall",
|
||||
"intervalOff": "Kein Wechsel",
|
||||
"intervalSeconds": "{n} Sekunden",
|
||||
"intervalMinutes": "{n} Minuten",
|
||||
"intervalMinutes": "{n, plural, one {# Minute} other {# Minuten}}",
|
||||
"orderLabel": "Reihenfolge der Bilder",
|
||||
"orderSequence": "Reihenfolge",
|
||||
"orderRandom": "Zufall",
|
||||
|
||||
@@ -347,7 +347,7 @@
|
||||
"intervalLabel": "Change interval",
|
||||
"intervalOff": "No change",
|
||||
"intervalSeconds": "{n} seconds",
|
||||
"intervalMinutes": "{n} minutes",
|
||||
"intervalMinutes": "{n, plural, one {# minute} other {# minutes}}",
|
||||
"orderLabel": "Image order",
|
||||
"orderSequence": "In order",
|
||||
"orderRandom": "Random",
|
||||
|
||||
Reference in New Issue
Block a user