Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

DEVPROD-1647 Add animation to details button when details menu items are changed. #424

Merged
merged 14 commits into from
Nov 14, 2023
82 changes: 82 additions & 0 deletions src/components/DetailsMenu/DetailsMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { act, waitFor } from "@testing-library/react";
import { QueryParams } from "constants/queryParams";
import { LogContextProvider, useLogContext } from "context/LogContext";
import { RenderFakeToastContext } from "context/toast/__mocks__";
import { useQueryParam } from "hooks/useQueryParam";
import { renderWithRouterMatch as render, screen, userEvent } from "test_utils";
import { renderComponentWithHook } from "test_utils/TestHooks";
import DetailsMenu from ".";

const wrapper = ({ children }: { children: React.ReactNode }) => (
<LogContextProvider initialLogLines={logs}>{children}</LogContextProvider>
);

const logs = [
"line 1",
"line 2",
"line 3",
"line 4",
"line 5",
"line 6",
"line 7",
];
/**
* `renderSharingMenu` renders the sharing menu with the default open prop
* @returns - hook and utils
*/
const renderDetailsMenu = () => {
const useCombinedHook = () => ({
useLogContext: useLogContext(),
useQueryParam: useQueryParam<number | undefined>(
QueryParams.UpperRange,
undefined
),
});
const { Component: MenuComponent, hook } = renderComponentWithHook(
useCombinedHook,
<DetailsMenu disabled={false} />
);
const { Component } = RenderFakeToastContext(<MenuComponent />);
const utils = render(<Component />, { wrapper });
return {
hook,
utils,
};
};

describe("detailsMenu", () => {
it("should render a details menu button", () => {
renderDetailsMenu();
expect(screen.getByText("Details")).toBeInTheDocument();
});
it("clicking on the details menu button should open the details menu", async () => {
const user = userEvent.setup();

renderDetailsMenu();
expect(screen.queryByDataCy("details-menu")).not.toBeInTheDocument();
const detailsButton = screen.getByRole("button", {
name: "Details",
});
expect(detailsButton).toBeEnabled();
await user.click(detailsButton);
expect(screen.getByDataCy("details-menu")).toBeInTheDocument();
});
it("updating search range should flash the details button", async () => {
jest.useFakeTimers();

const { hook } = renderDetailsMenu();
expect(screen.queryByDataCy("details-menu")).not.toBeInTheDocument();
const detailsButton = screen.getByRole("button", {
name: "Details",
});
expect(detailsButton).toBeEnabled();
act(() => {
hook.current.useQueryParam[1](1);
});
expect(detailsButton).toHaveAttribute("data-variant", "primary");
jest.runAllTimers();
await waitFor(() => {
expect(detailsButton).toHaveAttribute("data-variant", "default");
Comment on lines +77 to +80
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add assertions for all 3 states. flash off -> flash on -> flash off

});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`storyshots Storyshots components/DetailsMenu Default 1`] = `
exports[`storyshots Storyshots components/DetailsMenu/DetailsMenuCard/DetailsMenu Default 1`] = `
<div>
<div
class="leafygreen-ui-1ecxdul"
Expand Down Expand Up @@ -536,7 +536,7 @@ exports[`storyshots Storyshots components/DetailsMenu Default 1`] = `
</div>
`;

exports[`storyshots Storyshots components/DetailsMenu With Downloaded Log 1`] = `
exports[`storyshots Storyshots components/DetailsMenu/DetailsMenuCard/DetailsMenu With Downloaded Log 1`] = `
<div>
<div
class="leafygreen-ui-1ecxdul"
Expand Down
53 changes: 53 additions & 0 deletions src/components/DetailsMenu/DetailsMenuCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import styled from "@emotion/styled";
import { size } from "constants/tokens";
import ButtonRow from "./ButtonRow";
import CLIInstructions from "./CLIInstructions";
import SearchRangeInput from "./SearchRangeInput";
import {
CaseSensitiveToggle,
ExpandableRowsToggle,
FilterLogicToggle,
PrettyPrintToggle,
WrapToggle,
} from "./Toggles";

interface DetailsMenuProps {
["data-cy"]?: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
["data-cy"]?: string;
"data-cy"?: string;

}

const DetailsMenuCard: React.FC<DetailsMenuProps> = ({ "data-cy": dataCy }) => (
<Container data-cy={dataCy}>
<Row>
<Column>
<SearchRangeInput />
<WrapToggle />
<CaseSensitiveToggle />
</Column>
<Column>
<FilterLogicToggle />
<ExpandableRowsToggle />
<PrettyPrintToggle />
</Column>
</Row>
<ButtonRow />
<CLIInstructions />
</Container>
);

const Container = styled.div`
width: 700px;
padding: ${size.xs};
display: flex;
flex-direction: column;
`;

const Row = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
`;
const Column = styled.div`
width: 300px;
`;

export default DetailsMenuCard;
93 changes: 49 additions & 44 deletions src/components/DetailsMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,58 @@
import { useEffect, useState } from "react";
import styled from "@emotion/styled";
import { size } from "constants/tokens";
import ButtonRow from "./ButtonRow";
import CLIInstructions from "./CLIInstructions";
import SearchRangeInput from "./SearchRangeInput";
import {
CaseSensitiveToggle,
ExpandableRowsToggle,
FilterLogicToggle,
PrettyPrintToggle,
WrapToggle,
} from "./Toggles";
import { palette } from "@leafygreen-ui/palette";
import PopoverButton from "components/PopoverButton";
import { QueryParams } from "constants/queryParams";
import { useQueryParam } from "hooks/useQueryParam";
import DetailsMenuCard from "./DetailsMenuCard";

const { green } = palette;

interface DetailsMenuProps {
["data-cy"]?: string;
disabled?: boolean;
}
const DetailsMenu: React.FC<DetailsMenuProps> = ({ disabled, ...rest }) => {
const [lowerRange] = useQueryParam(QueryParams.LowerRange, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [lowerRange] = useQueryParam(QueryParams.LowerRange, 0);
const [lowerRange] = useQueryParam(QueryParams.LowerRange);

const [upperRange] = useQueryParam<undefined | number>(
QueryParams.UpperRange,
undefined
);
const [changeVisible, setChangeVisible] = useState(false);

const DetailsMenu: React.FC<DetailsMenuProps> = ({ "data-cy": dataCy }) => (
<DetailsMenuCard data-cy={dataCy}>
<Row>
<Column>
<SearchRangeInput />
<WrapToggle />
<CaseSensitiveToggle />
</Column>
<Column>
<FilterLogicToggle />
<ExpandableRowsToggle />
<PrettyPrintToggle />
</Column>
</Row>
<ButtonRow />
<CLIInstructions />
</DetailsMenuCard>
);
useEffect(() => {
setChangeVisible(true);
const timer = setTimeout(() => {
setChangeVisible(false);
}, 2000);
return () => clearTimeout(timer);
}, [lowerRange, upperRange]);

const DetailsMenuCard = styled.div`
width: 700px;
padding: ${size.xs};
display: flex;
flex-direction: column;
`;
return (
<AnimatedPopoverButton
buttonText="Details"
disabled={disabled}
variant={changeVisible ? "primary" : "default"}
{...rest}
>
<DetailsMenuCard data-cy="details-menu" />
</AnimatedPopoverButton>
);
};

const Row = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
const AnimatedPopoverButton = styled(PopoverButton)`
/* Glow animation */
${({ variant }) =>
variant === "primary" &&
`
animation: glow 1s ease-in-out infinite alternate;
@keyframes glow {
from {
box-shadow: 0 0 0px ${green.base};
}
to {
box-shadow: 0 0 20px ${green.base};
}
}
`}
`;
const Column = styled.div`
width: 300px;
`;

export default DetailsMenu;
11 changes: 2 additions & 9 deletions src/components/NavBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import IconButton from "@leafygreen-ui/icon-button";
import { palette } from "@leafygreen-ui/palette";
import DetailsMenu from "components/DetailsMenu";
import Icon from "components/Icon";
import PopoverButton from "components/PopoverButton";
import Search from "components/Search";
import ShortcutModal from "components/ShortcutModal";
import { StyledLink } from "components/styles";
Expand Down Expand Up @@ -41,13 +40,7 @@ const NavBar: React.FC = () => {
>
<Icon glyph="InfoWithCircle" />
</IconButton>
<StyledButton
buttonText="Details"
data-cy="details-button"
disabled={!hasLogs}
>
<DetailsMenu data-cy="details-menu" />
</StyledButton>
<StyledDetailsMenu data-cy="details-button" disabled={!hasLogs} />
{isDevelopmentBuild() && (
<Button
onClick={logoutAndRedirect}
Expand Down Expand Up @@ -91,7 +84,7 @@ const LinkContainer = styled.div`
gap: ${size.l};
`;

const StyledButton = styled(PopoverButton)`
const StyledDetailsMenu = styled(DetailsMenu)`
margin: 0 ${size.xs};
`;

Expand Down
3 changes: 3 additions & 0 deletions src/components/PopoverButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const PopoverButton: React.FC<PopoverButtonProps> = ({
buttonText,
children,
onClick,
variant,
...rest
}) => {
const [isOpen, setIsOpen] = useState(false);
Expand All @@ -31,9 +32,11 @@ const PopoverButton: React.FC<PopoverButtonProps> = ({
return (
<Button
ref={buttonRef}
data-variant={variant}
onClick={handleClick}
rightGlyph={<Icon glyph="CaretDown" />}
size="small"
variant={variant}
{...rest}
>
{buttonText}
Expand Down
2 changes: 2 additions & 0 deletions src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2105,6 +2105,7 @@ export type RepoRef = {
manualPrTestingEnabled: Scalars["Boolean"]["output"];
notifyOnBuildFailure: Scalars["Boolean"]["output"];
owner: Scalars["String"]["output"];
parsleyFilters?: Maybe<Array<ParsleyFilter>>;
patchTriggerAliases?: Maybe<Array<PatchTriggerAlias>>;
patchingDisabled: Scalars["Boolean"]["output"];
perfEnabled: Scalars["Boolean"]["output"];
Expand Down Expand Up @@ -2146,6 +2147,7 @@ export type RepoRefInput = {
manualPrTestingEnabled?: InputMaybe<Scalars["Boolean"]["input"]>;
notifyOnBuildFailure?: InputMaybe<Scalars["Boolean"]["input"]>;
owner?: InputMaybe<Scalars["String"]["input"]>;
parsleyFilters?: InputMaybe<Array<ParsleyFilterInput>>;
patchTriggerAliases?: InputMaybe<Array<PatchTriggerAliasInput>>;
patchingDisabled?: InputMaybe<Scalars["Boolean"]["input"]>;
perfEnabled?: InputMaybe<Scalars["Boolean"]["input"]>;
Expand Down