fleet/frontend/layouts/CoreLayout/CoreLayout.tsx
jacobshandling d3ccb51755
UI - Improve UX of Flash messages (#22836)
## #22661 


![ezgif-6-71e48912ae](https://github.com/user-attachments/assets/01144620-0eba-48f0-9254-cc4795fde9fd)

- Update `FlashMessage` behavior to, by default, hide itself when the
user performs any URL-changing navigation
- Add `persistOnPageChange` option to `renderFlash` API and associated
notification context and reducer logic, allowing override of this
behavior on a per-call basis
- Ensure proper order of evaluation of URL changes and render flash
action dispatches on the event loop
- Clean up legacy unused "undo"-related arguments and logic
- Allow the user to click in the same horizontal dimension as a flash
message
- Other misc. cleanup and refactoring

[Demo - messages hidden on page (any URL)
change](https://www.loom.com/share/1e884b6ba11c4b59bc74f51df3690131?sid=9b53e78b-6535-4541-b676-377760366cf4)

- [x] Changes file added for user-visible changes in `changes/`,
- [x] Manual QA for all new/changed functionality

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2024-10-22 10:52:20 -07:00

98 lines
2.6 KiB
TypeScript

import React, { useContext } from "react";
import { InjectedRouter } from "react-router";
import UnsupportedScreenSize from "layouts/UnsupportedScreenSize";
import { AppContext } from "context/app";
import { NotificationContext } from "context/notification";
import { TableContext } from "context/table";
import paths from "router/paths";
import useDeepEffect from "hooks/useDeepEffect";
import FlashMessage from "components/FlashMessage";
import SiteTopNav from "components/top_nav/SiteTopNav";
import { QueryParams } from "utilities/url";
interface ICoreLayoutProps {
children: React.ReactNode;
router: InjectedRouter; // v3
// TODO: standardize typing and usage of location across app components
location: {
pathname: string;
search: string;
hash?: string;
query: QueryParams;
};
}
const CoreLayout = ({ children, router, location }: ICoreLayoutProps) => {
const { config, currentUser } = useContext(AppContext);
const { notification, hideFlash } = useContext(NotificationContext);
const { setResetSelectedRows } = useContext(TableContext);
// on success of an action, the table will reset its checkboxes.
// setTimeout is to help with race conditions as table reloads
// in some instances (i.e. Manage Hosts)
useDeepEffect(() => {
if (notification?.alertType === "success") {
setTimeout(() => {
setResetSelectedRows(true);
setTimeout(() => {
setResetSelectedRows(false);
}, 300);
}, 0);
}
}, [notification]);
const onLogoutUser = async () => {
const { LOGOUT } = paths;
router.push(LOGOUT);
};
const onUserMenuItemClick = (path: string) => {
return (evt: React.MouseEvent<HTMLButtonElement>) => {
evt.preventDefault();
if (path.indexOf("http") !== -1) {
global.window.open(path, "_blank");
return false;
}
router.push(path);
return false;
};
};
const fullWidthFlash = !currentUser;
if (!currentUser || !config) {
return null;
}
return (
<div className="app-wrap">
<UnsupportedScreenSize />
<nav className="site-nav-container">
<SiteTopNav
config={config}
currentUser={currentUser}
location={location}
onLogoutUser={onLogoutUser}
onUserMenuItemClick={onUserMenuItemClick}
/>
</nav>
<div className="core-wrapper">
<FlashMessage
fullWidth={fullWidthFlash}
notification={notification}
onRemoveFlash={hideFlash}
pathname={location.pathname}
/>
{children}
</div>
</div>
);
};
export default CoreLayout;