Move toolbar controls to the top navigation bar

Moves the edit, search, and date controls from the Schedule section's sticky header to the main navigation bar using React portals.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ebb081ea-2bdd-4a2c-837f-1f4c4cdb67de
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/jwG0a8d
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
riyadhafraa
2026-05-04 17:50:11 +00:00
parent 1ee3f9a55f
commit 406f4ef693
2 changed files with 85 additions and 81 deletions
@@ -11,6 +11,7 @@ import {
type ReactNode,
type SetStateAction,
} from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import { useLocation } from "wouter";
import { useQuery, useQueryClient } from "@tanstack/react-query";
@@ -897,29 +898,33 @@ function ExecutiveMeetingsPageInner() {
</div>
<nav className="border-t border-gray-100 bg-white overflow-x-auto">
<div className="max-w-screen-2xl mx-auto px-2 sm:px-4 flex gap-1 sm:gap-2 min-w-max">
{SECTIONS.filter(({ key }) => isSectionVisible(key, me)).map(
({ key, icon: Icon }) => {
const active = section === key;
return (
<button
key={key}
type="button"
onClick={() => setSection(key)}
className={
"flex items-center gap-1.5 px-3 sm:px-4 py-2.5 text-xs sm:text-sm whitespace-nowrap border-b-2 transition-colors " +
(active
? "border-[#0B1E3F] text-[#0B1E3F] font-semibold"
: "border-transparent text-muted-foreground hover:text-foreground")
}
data-testid={`em-nav-${key}`}
>
<Icon className="w-4 h-4" />
{t(`executiveMeetings.nav.${key}`)}
</button>
);
},
)}
<div className="max-w-screen-2xl mx-auto px-2 sm:px-4 flex items-center gap-1 sm:gap-2">
<div className="flex gap-1 sm:gap-2 min-w-max">
{SECTIONS.filter(({ key }) => isSectionVisible(key, me)).map(
({ key, icon: Icon }) => {
const active = section === key;
return (
<button
key={key}
type="button"
onClick={() => setSection(key)}
className={
"flex items-center gap-1.5 px-3 sm:px-4 py-2.5 text-xs sm:text-sm whitespace-nowrap border-b-2 transition-colors " +
(active
? "border-[#0B1E3F] text-[#0B1E3F] font-semibold"
: "border-transparent text-muted-foreground hover:text-foreground")
}
data-testid={`em-nav-${key}`}
>
<Icon className="w-4 h-4" />
{t(`executiveMeetings.nav.${key}`)}
</button>
);
},
)}
</div>
<div className="flex-1" />
<div id="em-toolbar-portal" className="flex items-center gap-2 py-1 print:hidden" />
</div>
</nav>
</header>
@@ -2431,70 +2436,69 @@ function ScheduleSection({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [orderedMeetings.length, visibleColumns.length]);
const portalTarget = typeof document !== "undefined" ? document.getElementById("em-toolbar-portal") : null;
const toolbarControls = (
<>
{canMutate && (
<button
type="button"
onClick={() => setEditMode(!editMode)}
aria-pressed={editMode}
aria-label={
editMode
? t("executiveMeetings.schedule.saveToggleAria")
: t("executiveMeetings.schedule.editToggleAria")
}
title={
editMode
? t("executiveMeetings.schedule.saveToggleOn")
: t("executiveMeetings.schedule.saveToggleOff")
}
data-testid="em-edit-mode-toggle"
className={
"inline-flex items-center justify-center rounded-md border w-8 h-8 transition-colors focus:outline-none focus:ring-2 focus:ring-[#0B1E3F]/40 " +
(editMode
? "bg-[#0B1E3F] text-white border-[#0B1E3F] hover:bg-[#0B1E3F]/90"
: "bg-white text-[#0B1E3F] border-gray-300 hover:bg-gray-50")
}
>
{editMode ? (
<Check className="w-4 h-4" />
) : (
<Pencil className="w-4 h-4" />
)}
</button>
)}
<SearchToggle
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
placeholder={t("executiveMeetings.schedule.searchPlaceholder")}
ariaLabel={t("executiveMeetings.schedule.searchAria")}
clearLabel={t("executiveMeetings.schedule.searchClear")}
isRtl={isRtl}
/>
<label className="flex items-center gap-2 text-sm">
<span className="text-muted-foreground">{t("executiveMeetings.date")}</span>
<input
type="date"
value={date}
onChange={(e) => onDateChange(e.target.value)}
className="border border-gray-300 rounded px-2 py-1 text-sm bg-white"
/>
</label>
</>
);
return (
<div className="space-y-4 print:space-y-2">
{portalTarget && createPortal(toolbarControls, portalTarget)}
<div
ref={headingRef}
data-testid="em-schedule-heading-bar"
className="sticky z-30 bg-[#f4f6fb] -mx-3 sm:-mx-6 px-3 sm:px-6 py-2 shadow-[0_4px_6px_-4px_rgba(11,30,63,0.15)] flex items-center justify-between gap-3 flex-wrap print:hidden print:!static print:!shadow-none print:!mx-0 print:!px-0"
className="print:hidden"
style={{ top: "var(--em-header-h, 0px)" }}
>
<h2
className="text-xl sm:text-2xl font-bold text-[#0B1E3F]"
data-testid="em-schedule-heading"
>
{t("executiveMeetings.scheduleHeading")}
</h2>
<div className="flex items-center gap-2 flex-wrap">
{canMutate && (
<button
type="button"
onClick={() => setEditMode(!editMode)}
aria-pressed={editMode}
aria-label={
editMode
? t("executiveMeetings.schedule.saveToggleAria")
: t("executiveMeetings.schedule.editToggleAria")
}
title={
editMode
? t("executiveMeetings.schedule.saveToggleOn")
: t("executiveMeetings.schedule.saveToggleOff")
}
data-testid="em-edit-mode-toggle"
className={
"inline-flex items-center justify-center rounded-md border w-8 h-8 transition-colors focus:outline-none focus:ring-2 focus:ring-[#0B1E3F]/40 " +
(editMode
? "bg-[#0B1E3F] text-white border-[#0B1E3F] hover:bg-[#0B1E3F]/90"
: "bg-white text-[#0B1E3F] border-gray-300 hover:bg-gray-50")
}
>
{editMode ? (
<Check className="w-4 h-4" />
) : (
<Pencil className="w-4 h-4" />
)}
</button>
)}
<SearchToggle
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
placeholder={t("executiveMeetings.schedule.searchPlaceholder")}
ariaLabel={t("executiveMeetings.schedule.searchAria")}
clearLabel={t("executiveMeetings.schedule.searchClear")}
isRtl={isRtl}
/>
<label className="flex items-center gap-2 text-sm">
<span className="text-muted-foreground">{t("executiveMeetings.date")}</span>
<input
type="date"
value={date}
onChange={(e) => onDateChange(e.target.value)}
className="border border-gray-300 rounded px-2 py-1 text-sm bg-white"
/>
</label>
</div>
</div>
/>
<div className="hidden print:block text-center mb-2">
<div className="font-bold text-lg">{t("executiveMeetings.title")}</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB