tx-os(#344): polish UpcomingMeetingAlert per user feedback
Four UI changes requested by an Arabic-speaking user on the floating
"اجتماع يبدأ قريباً" popup:
1. Time window LTR — wrap the header `alert-time-window` span in
`dir="ltr"` so the start time always appears on the left and the
end on the right, even inside the Arabic RTL UI. Prevents the
"م 3:20 – م 3:10" reversal seen in the user's screenshot.
2. Drop the duplicate "Time" row inside DetailsPanel — the same
start/end window is already shown in the header strip. Removed
the `if (timeWindow) rows.push({ key: "time", ... })` block; the
`timeWindow` local was deleted along with it. Header is now the
single source of truth.
3. Remove the labelled "تجاهل التنبيه" footer button. The X close
button in the header still calls `handleDismiss`, so users can
still dismiss; the action bar is now just Done / Postpone /
Details. `handleDismiss`, `dismissToast`, and the i18n keys are
left intact for the X button.
4. Make the "يبدأ خلال N دقائق" / "يبدأ الآن" countdown blink red.
Added a dedicated `@keyframes em-blink` (1s, opacity 1 → 0.15 →
1) and `.em-blink` class in `src/index.css`, with a
`prefers-reduced-motion` override that pins it at full opacity.
Countdown span uses `text-red-600 em-blink font-semibold`. The
previously-used `accentText` local had no remaining consumers,
so it was removed (replaced with an explanatory comment).
Files:
- artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx
- artifacts/tx-os/src/index.css
Verification: `pnpm -C artifacts/tx-os exec tsc --noEmit` clean.
Architect review APPROVED (PASS). No tests referenced the removed
`alert-dismiss` / `alert-details-time` testids.
Pre-existing failures (unrelated): api-server workflow and the
combined `test` workflow continue to fail with the same upstream
issues observed in #342/#343 trajectory; this change does not
touch the API server or e2e test infrastructure.
This commit is contained in:
@@ -481,7 +481,8 @@ export function UpcomingMeetingAlert() {
|
||||
const dividerColor = hexToRgba(alertPrefs.accent, 0.45);
|
||||
const closeHoverBg = hexToRgba(alertPrefs.accent, 0.25);
|
||||
const detailsBg = hexToRgba(alertPrefs.accent, 0.12);
|
||||
const accentText = alertPrefs.accent;
|
||||
// #344: `accentText` was removed because the only consumer (the
|
||||
// "starts in N minutes" headline) now uses a fixed red blink class.
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -561,8 +562,7 @@ export function UpcomingMeetingAlert() {
|
||||
</div>
|
||||
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
|
||||
<span
|
||||
className="font-semibold"
|
||||
style={{ color: accentText }}
|
||||
className="em-blink font-semibold text-red-600"
|
||||
data-testid="alert-countdown"
|
||||
>
|
||||
{headline}
|
||||
@@ -571,6 +571,12 @@ export function UpcomingMeetingAlert() {
|
||||
<span
|
||||
className="inline-flex items-center gap-1 opacity-80"
|
||||
data-testid="alert-time-window"
|
||||
// The time window is always rendered LTR (start on the
|
||||
// left, end on the right) even inside the Arabic UI —
|
||||
// requested explicitly by the user (#344). The clock
|
||||
// icon precedes the digits in source order; with
|
||||
// dir="ltr" that puts it visually on the left.
|
||||
dir="ltr"
|
||||
>
|
||||
<Clock className="h-3 w-3" aria-hidden="true" />
|
||||
{meeting.endTime
|
||||
@@ -623,16 +629,13 @@ export function UpcomingMeetingAlert() {
|
||||
{t("executiveMeetings.alert.postpone")}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleDismiss}
|
||||
disabled={savingAction !== null}
|
||||
data-testid="alert-dismiss"
|
||||
>
|
||||
{t("executiveMeetings.alert.dismiss")}
|
||||
</Button>
|
||||
{/*
|
||||
#344: The labelled "Dismiss alert" button was removed at
|
||||
the user's request — the X close button in the header
|
||||
(which still calls handleDismiss) is the single way to
|
||||
hide the popup, keeping the action bar focused on
|
||||
"Done" / "Postpone" / "Details".
|
||||
*/}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -1771,13 +1774,10 @@ function DetailsPanel({
|
||||
}
|
||||
})();
|
||||
|
||||
// #307: Pre-compute the rendered time-window string so the row is
|
||||
// skipped entirely when the meeting has no scheduled times.
|
||||
const timeWindow = meeting.startTime
|
||||
? meeting.endTime
|
||||
? `${formatTime(meeting.startTime, lang)} – ${formatTime(meeting.endTime, lang)}`
|
||||
: formatTime(meeting.startTime, lang)
|
||||
: null;
|
||||
// #344: The "time" row in the details panel duplicated the time
|
||||
// window already shown in the header strip, so it was removed at
|
||||
// the user's request. Header is the single source of truth for the
|
||||
// start/end times.
|
||||
|
||||
// Each row renders a label cell + a value cell inside the parent
|
||||
// CSS grid. Skipping rows whose value is missing keeps the table
|
||||
@@ -1791,15 +1791,6 @@ function DetailsPanel({
|
||||
};
|
||||
const rows: Row[] = [];
|
||||
|
||||
if (timeWindow) {
|
||||
rows.push({
|
||||
key: "time",
|
||||
icon: <Clock className="h-3.5 w-3.5 opacity-70" aria-hidden="true" />,
|
||||
label: t("executiveMeetings.alert.detailsTime"),
|
||||
value: <span data-testid="alert-details-time">{timeWindow}</span>,
|
||||
});
|
||||
}
|
||||
|
||||
if (meeting.location && meeting.location.trim()) {
|
||||
rows.push({
|
||||
key: "location",
|
||||
|
||||
@@ -339,3 +339,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* #344: Attention-grabbing blink for the "starts in N minutes" headline
|
||||
* inside the floating UpcomingMeetingAlert. Tailwind's `animate-pulse`
|
||||
* only fades to opacity 0.5, which doesn't read as a true blink, so we
|
||||
* ship a dedicated keyframe that drops to 0.15 and back. Respects the
|
||||
* user's reduced-motion preference by stopping at full opacity.
|
||||
*/
|
||||
@keyframes em-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.15; }
|
||||
}
|
||||
.em-blink {
|
||||
animation: em-blink 1s ease-in-out infinite;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.em-blink { animation: none; opacity: 1; }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user