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:
@@ -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 |
Reference in New Issue
Block a user