Skip to content

Commit

Permalink
Popover lifecycle callbacks [LG-3734] (#2067)
Browse files Browse the repository at this point in the history
* LG-3699, LG-3723: DP keyboard interactions [WIP] (#2052)

* WIP

* comments

* update comments

* WIP test

* bump lib

* WIP test

* passing test

* comments

* remove param

* clean up

* creates utilities

* Adds popover Transition lifecycle hooks

* Create quiet-eggs-carry.md

* mv Popover content

* mv PopoverContext content

* set isPopoverOpen in onEntered & onExited CBs

* Update PopoverContext.spec.tsx

* Update Popover.spec.tsx

* Update Popover.spec.tsx

* use transitionDuration in Popover timeout

* extends div props in Popover

* user renderHook from RTL/react-hooks

* adds type specs

* use PopoverComponentProps

* Update package.json

* Update Popover.spec.tsx

* Update Popover.tsx

---------

Co-authored-by: Shaneeza <[email protected]>
  • Loading branch information
TheSonOfThomp and shaneeza authored Nov 3, 2023
1 parent 5064d20 commit 99848a0
Show file tree
Hide file tree
Showing 25 changed files with 484 additions and 227 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-rivers-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/lib': minor
---

Adds `pickAndOnit` helper function
5 changes: 5 additions & 0 deletions .changeset/late-ducks-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/popover': patch
---

Toggles `isPopoverOpen` in `PopoverContext` in the `onEntered` and `onExited` lifecycle callbacks, to better reflect the true state of the popover state
5 changes: 5 additions & 0 deletions .changeset/quiet-eggs-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/popover': minor
---

Adds `onEnter*` and `onExit*` callbacks to Popover. These callbacks are provided to the internal React Transition element. See [ReactTransitionGroup docs](https://reactcommunity.org/react-transition-group/transition#Transition-prop-onEnter) for more details.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@types/lodash": "^4.14.170",
"@types/react": "18.2.23",
"@types/react-dom": "18.2.8",
"@types/react-transition-group": "4.4.6",
"@types/react-transition-group": "4.4.8",
"chromatic": "^6.17.2",
"dotenv": "^10.0.0",
"gh-pages": "^3.1.0",
Expand Down
76 changes: 0 additions & 76 deletions packages/leafygreen-provider/src/PopoverContext.spec.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { PropsWithChildren } from 'react';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';

import { PopoverProvider, type PopoverState, usePopoverContext } from '.';

const childTestID = 'popover-provider';
const buttonTestId = 'test-button';

function TestContextComponent() {
const { isPopoverOpen, setIsPopoverOpen } = usePopoverContext();

return (
<>
<div data-testid={childTestID}>
{isPopoverOpen !== undefined ? isPopoverOpen.toString() : ''}
</div>
<button
onClick={() => setIsPopoverOpen(true)}
data-testid={buttonTestId}
/>
</>
);
}

function renderProvider() {
const utils = render(
<PopoverProvider>
<TestContextComponent />
</PopoverProvider>,
);
const testChild = utils.getByTestId(childTestID);
return { ...utils, testChild };
}

describe('packages/leafygreen-provider/PopoverContext', () => {
test('only renders children in the DOM', () => {
const { container, testChild } = renderProvider();
expect(container.firstChild).toBe(testChild);
});

test('isPopoverOpen is initialized as false', () => {
const { testChild } = renderProvider();
expect(testChild.textContent).toBe('false');
});

test('when passed true, setIsPopoverOpen sets isPopoverOpen to true', () => {
const { testChild, getByTestId } = renderProvider();

// The button's click handler fires setIsPopoverOpen(true)
fireEvent.click(getByTestId(buttonTestId));

expect(testChild.textContent).toBe('true');
});
});

describe('usePopoverContext', () => {
test('is `false` by default', () => {
const { result } = renderHook(usePopoverContext);
expect(result.current.isPopoverOpen).toBeFalsy();
});

test('setter updates the value', async () => {
const { result, rerender } = renderHook<
PropsWithChildren<{}>,
PopoverState
>(usePopoverContext, {
wrapper: ({ children }) => <PopoverProvider>{children}</PopoverProvider>,
});

act(() => result.current.setIsPopoverOpen(true));
rerender();
await waitFor(() => {
expect(result.current.isPopoverOpen).toBe(true);
});
});

describe('with test component', () => {
function renderTestComponent() {
const utils = render(<TestContextComponent />);
const testChild = utils.getByTestId(childTestID);
return { ...utils, testChild };
}

test('when child is not a descendent of PopoverProvider, isPopoverOpen is false', () => {
const { testChild } = renderTestComponent();
expect(testChild.textContent).toBe('false');
});

test('when child is not a descendent of PopoverProvider, isPopoverOpen is false when setIsPopoverOpen sets isPopoverOpen to true', () => {
const { testChild, getByTestId } = renderTestComponent();

// The button's click handler fires setIsPopoverOpen(true)
fireEvent.click(getByTestId(buttonTestId));

expect(testChild.textContent).toBe('false');
});
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React, { createContext, useContext, useMemo, useState } from 'react';
import PropTypes from 'prop-types';

interface PopoverState {
export interface PopoverState {
/**
* Whether the most immediate popover ancestor is open
*/
isPopoverOpen: boolean;
/**
* Sets the internal state
* @internal
*/
setIsPopoverOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

Expand All @@ -11,6 +18,10 @@ export const PopoverContext = createContext<PopoverState>({
setIsPopoverOpen: () => {},
});

/**
* Access the popover state
* @returns `isPopoverOpen: boolean`
*/
export function usePopoverContext(): PopoverState {
return useContext(PopoverContext);
}
Expand All @@ -19,7 +30,11 @@ interface PopoverProviderProps {
children?: React.ReactNode;
}

function PopoverProvider({ children }: PopoverProviderProps) {
/**
* Creates a Popover context.
* Call `usePopoverContext` to access the popover state
*/
export function PopoverProvider({ children }: PopoverProviderProps) {
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);

const providerValue = useMemo(
Expand All @@ -40,5 +55,3 @@ function PopoverProvider({ children }: PopoverProviderProps) {
PopoverProvider.displayName = 'PopoverProvider';

PopoverProvider.propTypes = { children: PropTypes.node };

export default PopoverProvider;
6 changes: 6 additions & 0 deletions packages/leafygreen-provider/src/PopoverContext/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export {
PopoverContext,
PopoverProvider,
type PopoverState,
usePopoverContext,
} from './PopoverContext';
2 changes: 1 addition & 1 deletion packages/leafygreen-provider/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { useDarkMode, useDarkModeContext } from './DarkModeContext';
export { default, type LeafyGreenProviderProps } from './LeafyGreenContext';
export {
PopoverContext,
default as PopoverProvider,
PopoverProvider,
usePopoverContext,
} from './PopoverContext';
export {
Expand Down
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions packages/lib/src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { pickAndOmit } from './pickAndOmit';
export { consoleOnce } from './consoleOnce';
export { allEqual } from './allEqual';
23 changes: 23 additions & 0 deletions packages/lib/src/helpers/pickAndOmit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import omit from 'lodash/omit';
import pick from 'lodash/pick';

/**
* Returns an array of 2 objects,
* first, the result of calling `_.pick`,
* second, the result of calling `_.omit`
*
* e.g.
* ```js
* const obj = { a: 'A', b: 'B', c: 'C', d: 'D' }
* pickAndOmit(obj, ['a', 'b']) // [{a: "A", b: "B"}, {c: "C", d: "D"}]
* ```
*/
export const pickAndOmit = <T extends object, K extends keyof T>(
object: T,
keys: Array<K>,
): [Pick<T, K>, Omit<T, K>] => {
const picked = pick(object, keys);
const omitted = omit(object, keys);

return [picked, omitted];
};
12 changes: 2 additions & 10 deletions packages/lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import createUniqueClassName from './createUniqueClassName';
import getNodeTextContent from './getNodeTextContent';
import DarkModeProps, { Theme } from './DarkModeProps';
import getTheme from './getTheme';
import { allEqual } from './allEqual';
export * from './helpers';
export { validateChildren, isComponentType } from './validateChildren';
export { createSyntheticEvent } from './createSyntheticEvent';
export { consoleOnce } from './consoleOnce';

export {
type ExtendedComponentProps,
Expand All @@ -23,14 +22,7 @@ export {
IntrinsicElements,
} from './storybook';

export {
typeIs,
createUniqueClassName,
getNodeTextContent,
getTheme,
Theme,
allEqual,
};
export { typeIs, createUniqueClassName, getNodeTextContent, getTheme, Theme };
export type { DarkModeProps };

/** Helper type to extract an HTML element's valid props */
Expand Down
9 changes: 5 additions & 4 deletions packages/popover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"@leafygreen-ui/tokens": "^2.2.0",
"react-transition-group": "^4.4.5"
},
"devDependencies": {
"@leafygreen-ui/palette": "^4.0.7",
"@leafygreen-ui/button": "^21.0.9",
"@types/react-transition-group": "4.4.8"
},
"peerDependencies": {
"@leafygreen-ui/leafygreen-provider": "^3.1.10"
},
Expand All @@ -40,9 +45,5 @@
},
"bugs": {
"url": "https://jira.mongodb.org/projects/PD/summary"
},
"devDependencies": {
"@leafygreen-ui/palette": "^4.0.7",
"@leafygreen-ui/button": "^21.0.9"
}
}
Loading

0 comments on commit 99848a0

Please sign in to comment.